diff --git a/package.json b/package.json index 26ab2026..4e63ed82 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@electron/notarize": "^2.1.0", "@electron/osx-sign": "^1.0.5", "@electron/universal": "^2.0.1", + "@electron/windows-sign": "^1.0.0", "cross-spawn-windows-exe": "^1.2.0", "debug": "^4.0.1", "extract-zip": "^2.0.0", diff --git a/src/cli.js b/src/cli.js index 29f911d7..d5b8aae0 100644 --- a/src/cli.js +++ b/src/cli.js @@ -64,6 +64,19 @@ module.exports = { args.asar = true } + // windows-sign: `Object` or `true` + if (args.windowsSign === 'true') { + warning('--windows-sign does not take any arguments, it only has sub-properties (see --help)', args.quiet) + args.windowsSign = true + } else if (typeof args['windows-sign'] === 'object') { + if (Array.isArray(args['windows-sign'])) { + warning('Remove --windows-sign (the bare flag) from the command line, only specify sub-properties (see --help)', args.quiet) + } else { + // Keep kebab case of sub properties + args.windowsSign = args['windows-sign'] + } + } + // osx-sign: `Object` or `true` if (args.osxSign === 'true') { warning('--osx-sign does not take any arguments, it only has sub-properties (see --help)', args.quiet) diff --git a/src/index.d.ts b/src/index.d.ts index 7ae49450..c593822a 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -11,7 +11,8 @@ import { CreateOptions as AsarOptions } from '@electron/asar'; import { ElectronDownloadRequestOptions as ElectronDownloadOptions } from '@electron/get'; import { NotaryToolCredentials } from '@electron/notarize/lib/types'; -import { SignOptions } from '@electron/osx-sign/dist/esm/types'; +import { SignOptions as OSXInternalSignOptions } from '@electron/osx-sign/dist/esm/types'; +import { SignOptions as WindowsInternalSignOptions } from '@electron/windows-sign/dist/esm/types'; import type { makeUniversalApp } from '@electron/universal'; type MakeUniversalOpts = Parameters[0] @@ -108,12 +109,12 @@ declare namespace electronPackager { * @param callback - Must be called once you have completed your actions. */ ( - buildPath: string, - electronVersion: string, - platform: TargetArch, - arch: TargetArch, - callback: (err?: Error | null) => void - ) => void; + buildPath: string, + electronVersion: string, + platform: TargetArch, + arch: TargetArch, + callback: (err?: Error | null) => void + ) => void; type TargetDefinition = { arch: TargetArch; @@ -122,7 +123,7 @@ declare namespace electronPackager { type FinalizePackageTargetsHookFunction = (targets: TargetDefinition[], callback: (err?: Error | null) => void) => void; /** See the documentation for [`@electron/osx-sign`](https://npm.im/@electron/osx-sign#opts) for details. */ - type OsxSignOptions = Omit; + type OsxSignOptions = Omit; /** * See the documentation for [`@electron/universal`](https://github.com/electron/universal) @@ -146,6 +147,14 @@ declare namespace electronPackager { schemes: string[]; } + /** + * See the documentation for [`@electron/windows-sign`](https://github.com/electron/windows-sign) + * for details. + */ + interface WindowsSignOptions extends Omit { + continueOnError?: boolean + } + /** * A collection of application metadata to embed into the Windows executable. * @@ -487,6 +496,14 @@ declare namespace electronPackager { * * Defaults to the current working directory. */ + /** + * If present, signs Windows binary files. + * When the value is `true`, pass default configuration to the signing module. See + * [@electron/windows-sign](https://npm.im/@electron/windows-sign) for sub-option descriptions and + * their defaults. + * @category Windows + */ + windowsSign?: true | WindowsSignOptions; out?: string; /** * Whether to replace an already existing output directory for a given platform (`true`) or diff --git a/src/win32.js b/src/win32.js index ccaffe2c..ef909b58 100644 --- a/src/win32.js +++ b/src/win32.js @@ -3,6 +3,7 @@ const debug = require('debug')('electron-packager') const path = require('path') const { WrapperError } = require('cross-spawn-windows-exe') +const { sign } = require('@electron/windows-sign') const App = require('./platform') const common = require('./common') @@ -98,15 +99,51 @@ class WindowsApp extends App { } } + async signAppIfSpecified () { + const windowsSignOpt = this.opts.windowsSign + const windowsMetaData = this.opts.win32metadata + + if (windowsSignOpt) { + const signOpts = createSignOpts(windowsSignOpt, windowsMetaData, this.renamedAppPath) + debug(`Running @electron/windows-sign with the options ${JSON.stringify(signOpts)}`) + try { + await sign(signOpts) + } catch (err) { + // Although not signed successfully, the application is packed. + if (signOpts.continueOnError) { + common.warning(`Code sign failed; please retry manually. ${err}`, this.opts.quiet) + } else { + throw err + } + } + } + } + async create () { await this.initialize() await this.renameElectron() await this.copyExtraResources() await this.runRcedit() + await this.signAppIfSpecified() return this.move() } } +function createSignOpts (properties, windowsMetaData, appDirectory) { + let result = { appDirectory } + + if (typeof properties === 'object') { + result = { ...properties, appDirectory } + } + + // A little bit of convenience + if (windowsMetaData && windowsMetaData.FileDescription && !result.description) { + result.description = windowsMetaData.FileDescription + } + + return result +} + module.exports = { App: WindowsApp, updateWineMissingException: updateWineMissingException diff --git a/test/cli.js b/test/cli.js index dba513d2..15e910d6 100644 --- a/test/cli.js +++ b/test/cli.js @@ -86,6 +86,24 @@ test('CLI argument: --out without a value is the same as not passing --out', t = t.is(args.out, null) }) +test('CLI argument: --windows-sign=true', t => { + const args = cli.parseArgs(['--windows-sign=true']) + t.true(args.windowsSign) +}) + +test('CLI argument: --windows-sign and --windows-sign subproperties should not be mixed', t => { + util.setupConsoleWarnSpy() + cli.parseArgs(['--windows-sign', '--windows-sign.identity=identity']) + util.assertWarning(t, 'WARNING: Remove --windows-sign (the bare flag) from the command line, only specify sub-properties (see --help)') +}) + +test('CLI argument: --windows-sign is object', t => { + const args = cli.parseArgs([ + '--windows-sign.identity=identity' + ]) + t.is(args.windowsSign.identity, 'identity') +}) + test('CLI argument: --protocol with a corresponding --protocol-name', t => { const args = cli.parseArgs(['--protocol=foo', '--protocol-name=Foo']) t.deepEqual(args.protocols, [{ schemes: ['foo'], name: 'Foo' }]) diff --git a/usage.txt b/usage.txt index 484f6db9..928c41ba 100644 --- a/usage.txt +++ b/usage.txt @@ -103,6 +103,10 @@ osx-universal (macOS host platform only, requires --arch=universal) Options e.g. --osx-universal.mergeASARs="true" For info on supported values see https://electron.github.io/packager/main/modules/electronpackager.html#osxuniversaloptions +windows-sign Whether to sign Windows binary files with a codesigning certificate. You can either + pass --windows-sign by itself to use the default configuration or use dot notation to configure + a list of sub-properties, e.g. --windows-sign.certificateFile="C:\cert.pfx". + For info on supported values see https://npm.im/@electron/windows-sign. protocol URL protocol scheme to register the app as an opener of. For example, `--protocol=myapp` would register the app to open URLs such as `myapp://path`. This argument requires a `--protocol-name` diff --git a/yarn.lock b/yarn.lock index 65ec8756..83c92152 100644 --- a/yarn.lock +++ b/yarn.lock @@ -333,6 +333,15 @@ minimatch "^9.0.3" plist "^3.1.0" +"@electron/windows-sign@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@electron/windows-sign/-/windows-sign-1.0.0.tgz#f08a0a5d4b96840ab637ce11228a59ee8b665287" + integrity sha512-sdkQYAR/TQCEyYgz2jMbusL/ljdj6qA7vyIm/S9HICMAitXhXROFHUOLLgiORj1uiaf2EOB2U33DatGubUuZaQ== + dependencies: + debug "^4.3.4" + fs-extra "^11.1.1" + minimist "^1.2.8" + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.3.tgz#9e42981ef035beb3dd49add17acb96e8ff6f394c" @@ -3376,6 +3385,11 @@ minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== +minimist@^1.2.8: + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"