diff --git a/node_modules/@electron/universal/LICENSE b/node_modules/@electron/universal/LICENSE new file mode 100644 index 0000000..dc1402e --- /dev/null +++ b/node_modules/@electron/universal/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Contributors to the Electron project + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/@electron/universal/README.md b/node_modules/@electron/universal/README.md new file mode 100644 index 0000000..fd8dbab --- /dev/null +++ b/node_modules/@electron/universal/README.md @@ -0,0 +1,125 @@ +# @electron/universal + +> Create universal macOS Electron applications + +[![Test](https://github.com/electron/universal/actions/workflows/test.yml/badge.svg)](https://github.com/electron/universal/actions/workflows/test.yml) +[![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'; + +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' +}); +``` + +## FAQ + +#### The app is twice as big now, why? + +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? + +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). + +[`minimatch`]: https://github.com/isaacs/minimatch?tab=readme-ov-file#features diff --git a/node_modules/@electron/universal/dist/esm/integrity.d.ts b/node_modules/@electron/universal/dist/esm/integrity.d.ts new file mode 100644 index 0000000..bc5e439 --- /dev/null +++ b/node_modules/@electron/universal/dist/esm/integrity.d.ts @@ -0,0 +1,8 @@ +export interface HeaderHash { + algorithm: 'SHA256'; + hash: string; +} +export interface AsarIntegrity { + [key: string]: HeaderHash; +} +export declare function computeIntegrityData(contentsPath: string): Promise; diff --git a/node_modules/@electron/universal/dist/esm/integrity.js b/node_modules/@electron/universal/dist/esm/integrity.js new file mode 100644 index 0000000..f27f0ad --- /dev/null +++ b/node_modules/@electron/universal/dist/esm/integrity.js @@ -0,0 +1,23 @@ +import * as fs from 'fs-extra'; +import path from 'path'; +import { AppFileType, getAllAppFiles } from './file-utils'; +import { generateAsarIntegrity } from './asar-utils'; +export async function computeIntegrityData(contentsPath) { + const root = await fs.realpath(contentsPath); + const resourcesRelativePath = 'Resources'; + const resourcesPath = path.resolve(root, resourcesRelativePath); + const resources = await getAllAppFiles(resourcesPath); + const resourceAsars = resources + .filter((file) => file.type === AppFileType.APP_CODE) + .reduce((prev, file) => (Object.assign(Object.assign({}, prev), { [path.join(resourcesRelativePath, file.relativePath)]: path.join(resourcesPath, file.relativePath) })), {}); + // sort to produce constant result + const allAsars = Object.entries(resourceAsars).sort(([name1], [name2]) => name1.localeCompare(name2)); + const hashes = await Promise.all(allAsars.map(async ([, from]) => generateAsarIntegrity(from))); + const asarIntegrity = {}; + for (let i = 0; i < allAsars.length; i++) { + const [asar] = allAsars[i]; + asarIntegrity[asar] = hashes[i]; + } + return asarIntegrity; +} +//# sourceMappingURL=integrity.js.map \ No newline at end of file diff --git a/node_modules/@electron/universal/dist/esm/integrity.js.map b/node_modules/@electron/universal/dist/esm/integrity.js.map new file mode 100644 index 0000000..43791de --- /dev/null +++ b/node_modules/@electron/universal/dist/esm/integrity.js.map @@ -0,0 +1 @@ +{"version":3,"file":"integrity.js","sourceRoot":"","sources":["../../src/integrity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAerD,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,YAAoB;IAC7D,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAE7C,MAAM,qBAAqB,GAAG,WAAW,CAAC;IAC1C,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,qBAAqB,CAAC,CAAC;IAEhE,MAAM,SAAS,GAAG,MAAM,cAAc,CAAC,aAAa,CAAC,CAAC;IACtD,MAAM,aAAa,GAAG,SAAS;SAC5B,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,WAAW,CAAC,QAAQ,CAAC;SACpD,MAAM,CACL,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,iCACX,IAAI,KACP,CAAC,IAAI,CAAC,IAAI,CAAC,qBAAqB,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAC9D,aAAa,EACb,IAAI,CAAC,YAAY,CAClB,IACD,EACF,EAAE,CACH,CAAC;IAEJ,kCAAkC;IAClC,MAAM,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,CACvE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAC3B,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAChG,MAAM,aAAa,GAAkB,EAAE,CAAC;IACxC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACxC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QAC3B,aAAa,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;KACjC;IACD,OAAO,aAAa,CAAC;AACvB,CAAC"} \ No newline at end of file diff --git a/node_modules/@electron/universal/dist/esm/sha.d.ts b/node_modules/@electron/universal/dist/esm/sha.d.ts new file mode 100644 index 0000000..1c0daaa --- /dev/null +++ b/node_modules/@electron/universal/dist/esm/sha.d.ts @@ -0,0 +1 @@ +export declare const sha: (filePath: string) => Promise; diff --git a/node_modules/@electron/universal/dist/esm/sha.js b/node_modules/@electron/universal/dist/esm/sha.js new file mode 100644 index 0000000..466c072 --- /dev/null +++ b/node_modules/@electron/universal/dist/esm/sha.js @@ -0,0 +1,12 @@ +import * as fs from 'fs-extra'; +import * as crypto from 'crypto'; +import { pipeline } from 'stream/promises'; +import { d } from './debug'; +export const sha = async (filePath) => { + d('hashing', filePath); + const hash = crypto.createHash('sha256'); + hash.setEncoding('hex'); + await pipeline(fs.createReadStream(filePath), hash); + return hash.read(); +}; +//# sourceMappingURL=sha.js.map \ No newline at end of file diff --git a/node_modules/@electron/universal/dist/esm/sha.js.map b/node_modules/@electron/universal/dist/esm/sha.js.map new file mode 100644 index 0000000..3f78457 --- /dev/null +++ b/node_modules/@electron/universal/dist/esm/sha.js.map @@ -0,0 +1 @@ +{"version":3,"file":"sha.js","sourceRoot":"","sources":["../../src/sha.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,UAAU,CAAC;AAC/B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAE3C,OAAO,EAAE,CAAC,EAAE,MAAM,SAAS,CAAC;AAE5B,MAAM,CAAC,MAAM,GAAG,GAAG,KAAK,EAAE,QAAgB,EAAE,EAAE;IAC5C,CAAC,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC;IACzC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxB,MAAM,QAAQ,CAAC,EAAE,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;AACrB,CAAC,CAAC"} \ No newline at end of file diff --git a/node_modules/@electron/universal/entry-asar/has-asar.js b/node_modules/@electron/universal/entry-asar/has-asar.js new file mode 100644 index 0000000..666c23e --- /dev/null +++ b/node_modules/@electron/universal/entry-asar/has-asar.js @@ -0,0 +1,28 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const path_1 = __importDefault(require("path")); +if (process.arch === 'arm64') { + setPaths('arm64'); +} +else { + setPaths('x64'); +} +function setPaths(platform) { + // This should return the full path, ending in something like + // Notion.app/Contents/Resources/app.asar + const appPath = electron_1.app.getAppPath(); + const asarFile = `app-${platform}.asar`; + // Maybe we'll handle this in Electron one day + if (path_1.default.basename(appPath) === 'app.asar') { + const platformAppPath = path_1.default.join(path_1.default.dirname(appPath), asarFile); + // This is an undocumented API. It exists. + electron_1.app.setAppPath(platformAppPath); + } + process._archPath = require.resolve(`../${asarFile}`); +} +require(process._archPath); +//# sourceMappingURL=has-asar.js.map \ No newline at end of file diff --git a/node_modules/@electron/universal/entry-asar/has-asar.js.map b/node_modules/@electron/universal/entry-asar/has-asar.js.map new file mode 100644 index 0000000..cddaea2 --- /dev/null +++ b/node_modules/@electron/universal/entry-asar/has-asar.js.map @@ -0,0 +1 @@ +{"version":3,"file":"has-asar.js","sourceRoot":"","sources":["has-asar.ts"],"names":[],"mappings":";;;;;AAAA,uCAA+B;AAC/B,gDAAwB;AAExB,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE;IAC5B,QAAQ,CAAC,OAAO,CAAC,CAAC;CACnB;KAAM;IACL,QAAQ,CAAC,KAAK,CAAC,CAAC;CACjB;AAED,SAAS,QAAQ,CAAC,QAAgB;IAChC,6DAA6D;IAC7D,yCAAyC;IACzC,MAAM,OAAO,GAAG,cAAG,CAAC,UAAU,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,OAAO,QAAQ,OAAO,CAAC;IAExC,8CAA8C;IAC9C,IAAI,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,UAAU,EAAE;QACzC,MAAM,eAAe,GAAG,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;QAEnE,0CAA0C;QAC1C,cAAG,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;KACjC;IAED,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,QAAQ,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"} \ No newline at end of file diff --git a/node_modules/@electron/universal/entry-asar/no-asar.js b/node_modules/@electron/universal/entry-asar/no-asar.js new file mode 100644 index 0000000..9e3e909 --- /dev/null +++ b/node_modules/@electron/universal/entry-asar/no-asar.js @@ -0,0 +1,28 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const electron_1 = require("electron"); +const path_1 = __importDefault(require("path")); +if (process.arch === 'arm64') { + setPaths('arm64'); +} +else { + setPaths('x64'); +} +function setPaths(platform) { + // This should return the full path, ending in something like + // Notion.app/Contents/Resources/app + const appPath = electron_1.app.getAppPath(); + const appFolder = `app-${platform}`; + // Maybe we'll handle this in Electron one day + if (path_1.default.basename(appPath) === 'app') { + const platformAppPath = path_1.default.join(path_1.default.dirname(appPath), appFolder); + // This is an undocumented private API. It exists. + electron_1.app.setAppPath(platformAppPath); + } + process._archPath = require.resolve(`../${appFolder}`); +} +require(process._archPath); +//# sourceMappingURL=no-asar.js.map \ No newline at end of file diff --git a/node_modules/@electron/universal/entry-asar/no-asar.js.map b/node_modules/@electron/universal/entry-asar/no-asar.js.map new file mode 100644 index 0000000..04294ee --- /dev/null +++ b/node_modules/@electron/universal/entry-asar/no-asar.js.map @@ -0,0 +1 @@ +{"version":3,"file":"no-asar.js","sourceRoot":"","sources":["no-asar.ts"],"names":[],"mappings":";;;;;AAAA,uCAA+B;AAC/B,gDAAwB;AAExB,IAAI,OAAO,CAAC,IAAI,KAAK,OAAO,EAAE;IAC5B,QAAQ,CAAC,OAAO,CAAC,CAAC;CACnB;KAAM;IACL,QAAQ,CAAC,KAAK,CAAC,CAAC;CACjB;AAED,SAAS,QAAQ,CAAC,QAAgB;IAChC,6DAA6D;IAC7D,oCAAoC;IACpC,MAAM,OAAO,GAAG,cAAG,CAAC,UAAU,EAAE,CAAC;IACjC,MAAM,SAAS,GAAG,OAAO,QAAQ,EAAE,CAAC;IAEpC,8CAA8C;IAC9C,IAAI,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,KAAK,EAAE;QACpC,MAAM,eAAe,GAAG,cAAI,CAAC,IAAI,CAAC,cAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,SAAS,CAAC,CAAC;QAEpE,kDAAkD;QAClD,cAAG,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;KACjC;IAED,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC;AACzD,CAAC;AAED,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"} \ No newline at end of file diff --git a/node_modules/@electron/universal/package.json b/node_modules/@electron/universal/package.json new file mode 100644 index 0000000..3863083 --- /dev/null +++ b/node_modules/@electron/universal/package.json @@ -0,0 +1,74 @@ +{ + "name": "@electron/universal", + "version": "2.0.3", + "description": "Utility for creating Universal macOS applications from two x64 and arm64 Electron applications", + "main": "dist/cjs/index.js", + "module": "dist/esm/index.js", + "license": "MIT", + "keywords": [ + "electron", + "apple silicon", + "universal" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/electron/universal.git" + }, + "engines": { + "node": ">=16.4" + }, + "files": [ + "dist/*", + "entry-asar/*", + "!entry-asar/**/*.ts", + "README.md" + ], + "author": "Samuel Attard", + "publishConfig": { + "provenance": true + }, + "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", + "test": "jest", + "prepare": "husky install" + }, + "devDependencies": { + "@electron/get": "^3.0.0", + "@types/cross-zip": "^4.0.1", + "@types/debug": "^4.1.10", + "@types/fs-extra": "^11.0.3", + "@types/jest": "^29.5.7", + "@types/minimatch": "^5.1.2", + "@types/node": "^20.8.10", + "@types/plist": "^3.0.4", + "cross-zip": "^4.0.0", + "husky": "^8.0.3", + "jest": "^29.7.0", + "lint-staged": "^15.2.10", + "prettier": "^3.0.3", + "ts-jest": "^29.1.1", + "typedoc": "~0.25.13", + "typescript": "^5.2.2" + }, + "dependencies": { + "@electron/asar": "^3.3.1", + "@malept/cross-spawn-promise": "^2.0.0", + "debug": "^4.3.1", + "dir-compare": "^4.2.0", + "fs-extra": "^11.1.1", + "minimatch": "^9.0.3", + "plist": "^3.1.0" + }, + "lint-staged": { + "*.ts": [ + "prettier --write" + ] + }, + "resolutions": { + "jackspeak": "2.1.1" + } +} diff --git a/node_modules/@electron/windows-sign/bin/electron-windows-sign-usage.txt b/node_modules/@electron/windows-sign/bin/electron-windows-sign-usage.txt new file mode 100644 index 0000000..c5d9980 --- /dev/null +++ b/node_modules/@electron/windows-sign/bin/electron-windows-sign-usage.txt @@ -0,0 +1,43 @@ +NAME + electron-windows-sign -- code signing for Electron apps on Windows. + +SYNOPSIS + electron-windows-sign app [options ...] + +DESCRIPTION + app + Path to the application to sign. It must be a directory. + + certificate-file + Path to the certificate file (.pfx) to use for signing. + + certificate-password + Password to use for the certificate. + + sign-tool-path + Path to the signtool.exe binary. If not specified, the tool will attempt + use a built-in version. + + timestamp-server + URL of the timestamp server to use. If not specified, the tool will + attempt to use a built-in server (http://timestamp.digicert.com) + + description + Description to use for the signed files. Passed as /d to signtool.exe. + + website + URL of the website to use for the signed files. Passed as /du to + signtool.exe. + + sign-with-params + Additional parameters to pass to signtool.exe. This can be used to + specify additional certificates to use for cross-signing. + + automatically-select-certificate + Automatically select the best certificate to use for signing. On by default. + + help + Print this usage information. + + debug + Print additional debug information. diff --git a/node_modules/@electron/windows-sign/bin/electron-windows-sign.js b/node_modules/@electron/windows-sign/bin/electron-windows-sign.js new file mode 100644 index 0000000..4d04aee --- /dev/null +++ b/node_modules/@electron/windows-sign/bin/electron-windows-sign.js @@ -0,0 +1,47 @@ +#!/usr/bin/env node + +const fs = require('fs'); +const path = require('path'); +const args = require('minimist')(process.argv.slice(2), { + string: [ + 'certificate-file', + 'certificate-password', + 'sign-tool-path', + 'timestamp-server', + 'description', + 'website', + 'sign-with-params' + ], + boolean: [ + 'help', + 'debug' + ], + default: { + 'automatically-select-certificate': true + } +}); +const usage = fs.readFileSync(path.join(__dirname, 'electron-windows-sign-usage.txt')).toString(); +const sign = require('../').sign; + +args.app = args._.shift(); + +if (!args.app || args.help) { + console.log(usage); + process.exit(0); +} + +// Remove excess arguments +delete args._; +delete args.help; + +sign(args, function done(err) { + if (err) { + console.error('Sign failed:'); + if (err.message) console.error(err.message); + else if (err.stack) console.error(err.stack); + else console.log(err); + process.exit(1); + } + console.log('Application signed:', args.app); + process.exit(0); +}); diff --git a/node_modules/@electron/windows-sign/dist/cjs/files.d.ts b/node_modules/@electron/windows-sign/dist/cjs/files.d.ts new file mode 100644 index 0000000..d9bb4dd --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/files.d.ts @@ -0,0 +1,16 @@ +import { SignOptions } from './types'; +/** + * Recursively goes through an entire directory and returns an array + * of full paths for files ot sign. + * + * - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node) + * - Microsoft installers (.msi) + * - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle) + * - Catalog files (.cat) + * - Cabinet files (.cab) + * - Silverlight applications (.xap) + * - Scripts (.vbs, .wsf, .ps1) + * If configured: + * - JavaScript files (.js) + */ +export declare function getFilesToSign(options: SignOptions, dir?: string): Array; diff --git a/node_modules/@electron/windows-sign/dist/cjs/files.js b/node_modules/@electron/windows-sign/dist/cjs/files.js new file mode 100644 index 0000000..59a370e --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/files.js @@ -0,0 +1,66 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.getFilesToSign = void 0; +const path_1 = __importDefault(require("path")); +const fs_extra_1 = __importDefault(require("fs-extra")); +const IS_PE_REGEX = /\.(exe|dll|sys|efi|scr|node)$/i; +const IS_MSI_REGEX = /\.msi$/i; +const IS_PACKAGE_REGEX = /\.(appx|appxbundle|msix|msixbundle)$/i; +const IS_CATCAB_REGEX = /\.(cat|cab)$/i; +const IS_SILVERLIGHT_REGEX = /\.xap$/i; +const IS_SCRIPT_REGEX = /\.(vbs|wsf|ps1)$/i; +const IS_JS_REGEX = /\.js$/i; +/** + * Recursively goes through an entire directory and returns an array + * of full paths for files ot sign. + * + * - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node) + * - Microsoft installers (.msi) + * - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle) + * - Catalog files (.cat) + * - Cabinet files (.cab) + * - Silverlight applications (.xap) + * - Scripts (.vbs, .wsf, .ps1) + * If configured: + * - JavaScript files (.js) + */ +function getFilesToSign(options, dir) { + if (isSignOptionsForFiles(options)) { + return options.files; + } + dir = dir || options.appDirectory; + // Array of file paths to sign + const result = []; + // Iterate over the app directory, looking for files to sign + const files = fs_extra_1.default.readdirSync(dir); + const regexes = [ + IS_PE_REGEX, + IS_MSI_REGEX, + IS_PACKAGE_REGEX, + IS_CATCAB_REGEX, + IS_SILVERLIGHT_REGEX, + IS_SCRIPT_REGEX + ]; + if (options.signJavaScript) { + regexes.push(IS_JS_REGEX); + } + for (const file of files) { + const fullPath = path_1.default.resolve(dir, file); + if (fs_extra_1.default.statSync(fullPath).isDirectory()) { + // If it's a directory, recurse + result.push(...getFilesToSign(options, fullPath)); + } + else if (regexes.some((regex) => regex.test(file))) { + // If it's a match, add it to the list + result.push(fullPath); + } + } + return result; +} +exports.getFilesToSign = getFilesToSign; +function isSignOptionsForFiles(input) { + return !!input.files; +} diff --git a/node_modules/@electron/windows-sign/dist/cjs/index.d.ts b/node_modules/@electron/windows-sign/dist/cjs/index.d.ts new file mode 100644 index 0000000..6add955 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/index.d.ts @@ -0,0 +1,4 @@ +import { sign } from './sign'; +import { createSeaSignTool, SeaOptions, InternalSeaOptions } from './sea'; +import { HookFunction, OptionalHookOptions, OptionalSignToolOptions, SignOptions, SignToolOptions, SignOptionsForDirectory, SignOptionsForFiles } from './types'; +export { sign, SignOptions, SignToolOptions, HookFunction, OptionalSignToolOptions, OptionalHookOptions, createSeaSignTool, SeaOptions, InternalSeaOptions, SignOptionsForDirectory, SignOptionsForFiles }; diff --git a/node_modules/@electron/windows-sign/dist/cjs/index.js b/node_modules/@electron/windows-sign/dist/cjs/index.js new file mode 100644 index 0000000..db8cded --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/index.js @@ -0,0 +1,7 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createSeaSignTool = exports.sign = void 0; +const sign_1 = require("./sign"); +Object.defineProperty(exports, "sign", { enumerable: true, get: function () { return sign_1.sign; } }); +const sea_1 = require("./sea"); +Object.defineProperty(exports, "createSeaSignTool", { enumerable: true, get: function () { return sea_1.createSeaSignTool; } }); diff --git a/node_modules/@electron/windows-sign/dist/cjs/sea.d.ts b/node_modules/@electron/windows-sign/dist/cjs/sea.d.ts new file mode 100644 index 0000000..ac11ce0 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sea.d.ts @@ -0,0 +1,61 @@ +import { SignToolOptions } from './types'; +/** + * Options for signing with a Node.js single executable application. + * + * @category Single executable applications + */ +export interface SeaOptions { + /** + * Full path to the Node.js single executable application. Needs to end with `.exe`. + */ + path: string; + /** + * A binary to use. Will use the current executable (process.execPath) by default. + * + * @defaultValue The Node.js {@link https://nodejs.org/api/process.html#processexecpath | `process.execPath`} + */ + bin?: string; + /** + * Options to pass to SignTool. + */ + windowsSign: SignToolOptions; +} +/** + * This interface represents {@link SeaOptions} with all optional properties + * inferred by `@electron/windows-sign` if not passed in by the user. + * + * @category Single executable applications + */ +export interface InternalSeaOptions extends Required { + /** + * Directory of the Node.js single executable application. + */ + dir: string; + /** + * File name of the Node.js single executable application. + */ + filename: string; +} +/** + * cross-dir uses new Error() stacks + * to figure out our directory in a way + * that's somewhat cross-compatible. + * + * We can't just use __dirname because it's + * undefined in ESM - and we can't use import.meta.url + * because TypeScript won't allow usage unless you're + * _only_ compiling for ESM. + */ +export declare const DIRNAME: string; +/** + * Uses Node's "Single Executable App" functionality + * to create a Node-driven signtool.exe that calls this + * module. + * + * This is useful with other tooling that _always_ calls + * a signtool.exe to sign. Some of those tools cannot be + * easily configured, but we _can_ override their signtool.exe. + * + * @category Single executable applications + */ +export declare function createSeaSignTool(options?: Partial): Promise; diff --git a/node_modules/@electron/windows-sign/dist/cjs/sea.js b/node_modules/@electron/windows-sign/dist/cjs/sea.js new file mode 100644 index 0000000..0557836 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sea.js @@ -0,0 +1,220 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createSeaSignTool = exports.DIRNAME = void 0; +const cross_dirname_1 = require("cross-dirname"); +const path_1 = __importDefault(require("path")); +const os_1 = __importDefault(require("os")); +const fs_extra_1 = __importDefault(require("fs-extra")); +const postject_1 = __importDefault(require("postject")); +const spawn_1 = require("./spawn"); +const log_1 = require("./utils/log"); +/** + * cross-dir uses new Error() stacks + * to figure out our directory in a way + * that's somewhat cross-compatible. + * + * We can't just use __dirname because it's + * undefined in ESM - and we can't use import.meta.url + * because TypeScript won't allow usage unless you're + * _only_ compiling for ESM. + */ +exports.DIRNAME = (0, cross_dirname_1.getDirname)(); +const FILENAMES = { + SEA_CONFIG: 'sea-config.json', + SEA_MAIN: 'sea.js', + SEA_BLOB: 'sea.blob', + SEA_RECEIVER: 'receiver.mjs' +}; +const SEA_MAIN_SCRIPT = ` +const bin = "%PATH_TO_BIN%"; +const script = "%PATH_TO_SCRIPT%"; +const options = %WINDOWS_SIGN_OPTIONS% + +const { spawnSync } = require('child_process'); + +function main() { + console.log("@electron/windows-sign sea"); + console.log({ bin, script }); + + try { + const spawn = spawnSync( + bin, + [ script, JSON.stringify(options), JSON.stringify(process.argv.slice(1)) ], + { stdio: ['inherit', 'inherit', 'pipe'] } + ); + + if (spawn.status !== 0) { + throw new Error(\`Spawn failed with code: \${spawn.status}. Stderr: \${spawn.stderr}\`); + } + } catch (error) { + // Do not rethrow the error or write it to stdout/stderr. Then the process won't terminate. + // See: https://github.com/electron/windows-sign/pull/48 + process.exit(1); + } +} + +main(); +`; +const SEA_RECEIVER_SCRIPT = ` +import { sign } from '@electron/windows-sign'; +import fs from 'fs-extra'; +import path from 'path'; + +const logPath = path.join('electron-windows-sign.log'); +const options = JSON.parse(process.argv[2]); +const signArgv = JSON.parse(process.argv[3]); +const files = signArgv.slice(-1); + +try { + fs.appendFileSync(logPath, \`\\nCalled with: \${JSON.stringify(process.argv, null, 2)}\`); + + sign({ ...options, files }) + .then((result) => { + fs.appendFileSync(logPath, \`\\nSuccessfully signed with result: \${result}\`); + }) + .catch((error) => { + fs.appendFileSync(logPath, \`\\nError from sign: \${error}\`); + throw new Error(error); + }); +} catch (error) { + fs.appendFileSync(logPath, \`\\Error invoking sign: \${error}\`); + throw new Error(error); +} +`; +/** + * Uses Node's "Single Executable App" functionality + * to create a Node-driven signtool.exe that calls this + * module. + * + * This is useful with other tooling that _always_ calls + * a signtool.exe to sign. Some of those tools cannot be + * easily configured, but we _can_ override their signtool.exe. + * + * @category Single executable applications + */ +async function createSeaSignTool(options = {}) { + checkCompatibility(); + const requiredOptions = await getOptions(options); + await createFiles(requiredOptions); + await createBlob(requiredOptions); + await createBinary(requiredOptions); + await createSeaReceiver(requiredOptions); + await cleanup(requiredOptions); + return requiredOptions; +} +exports.createSeaSignTool = createSeaSignTool; +async function createSeaReceiver(options) { + const receiverPath = path_1.default.join(options.dir, FILENAMES.SEA_RECEIVER); + await fs_extra_1.default.ensureFile(receiverPath); + await fs_extra_1.default.writeFile(receiverPath, SEA_RECEIVER_SCRIPT); +} +async function createFiles(options) { + const { dir, bin } = options; + const receiverPath = path_1.default.join(options.dir, FILENAMES.SEA_RECEIVER); + // sea-config.json + await fs_extra_1.default.outputJSON(path_1.default.join(dir, FILENAMES.SEA_CONFIG), { + main: FILENAMES.SEA_MAIN, + output: FILENAMES.SEA_BLOB, + disableExperimentalSEAWarning: true + }, { + spaces: 2 + }); + // signtool.js + const binPath = bin || process.execPath; + const script = SEA_MAIN_SCRIPT + .replace('%PATH_TO_BIN%', escapeMaybe(binPath)) + .replace('%PATH_TO_SCRIPT%', escapeMaybe(receiverPath)) + .replace('%WINDOWS_SIGN_OPTIONS%', JSON.stringify(options.windowsSign)); + await fs_extra_1.default.outputFile(path_1.default.join(dir, FILENAMES.SEA_MAIN), script); +} +async function createBlob(options) { + const args = ['--experimental-sea-config', 'sea-config.json']; + const bin = process.execPath; + const cwd = options.dir; + (0, log_1.log)(`Calling ${bin} with options:`, args); + const { stderr, stdout } = await (0, spawn_1.spawnPromise)(bin, args, { + cwd + }); + (0, log_1.log)('stdout:', stdout); + (0, log_1.log)('stderr:', stderr); +} +async function createBinary(options) { + const { dir, filename } = options; + (0, log_1.log)(`Creating ${filename} in ${dir}`); + // Copy Node over + const seaPath = path_1.default.join(dir, filename); + await fs_extra_1.default.copyFile(process.execPath, seaPath); + // Remove the Node signature + const signtool = path_1.default.join(exports.DIRNAME, '../../vendor/signtool.exe'); + await (0, spawn_1.spawnPromise)(signtool, [ + 'remove', + '/s', + seaPath + ]); + // Inject the blob + const blob = await fs_extra_1.default.readFile(path_1.default.join(dir, FILENAMES.SEA_BLOB)); + await postject_1.default.inject(seaPath, 'NODE_SEA_BLOB', blob, { + sentinelFuse: 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2' + }); +} +async function cleanup(options) { + const { dir } = options; + const toRemove = [ + FILENAMES.SEA_BLOB, + FILENAMES.SEA_MAIN, + FILENAMES.SEA_CONFIG + ]; + for (const file of toRemove) { + try { + await fs_extra_1.default.remove(path_1.default.join(dir, file)); + } + catch (error) { + console.warn(`Tried and failed to remove ${file}. Continuing.`, error); + } + } +} +async function getOptions(options) { + const cloned = { ...options }; + if (!cloned.path) { + cloned.path = path_1.default.join(os_1.default.homedir(), '.electron', 'windows-sign', 'sea.exe'); + await fs_extra_1.default.ensureFile(cloned.path); + } + if (!cloned.bin) { + cloned.bin = process.execPath; + } + if (!cloned.windowsSign) { + throw new Error('Did not find windowsSign options, which are required'); + } + return { + path: cloned.path, + dir: path_1.default.dirname(cloned.path), + filename: path_1.default.basename(cloned.path), + bin: cloned.bin, + windowsSign: cloned.windowsSign + }; +} +/** + * Ensures that the current Node.js version supports SEA app generation and errors if not. + */ +function checkCompatibility() { + const version = process.versions.node; + const split = version.split('.'); + const major = parseInt(split[0], 10); + if (major >= 20) { + return true; + } + throw new Error(`Your Node.js version (${process.version}) does not support Single Executable Applications. Please upgrade your version of Node.js.`); +} +/** Make sure that the input string has escaped backwards slashes + * - but never double-escaped backwards slashes. + */ +function escapeMaybe(input) { + const result = input.split(path_1.default.sep).join('\\\\'); + if (result.includes('\\\\\\\\')) { + throw new Error(`Your passed input ${input} contains escaped slashes. Please do not escape them`); + } + return result; +} diff --git a/node_modules/@electron/windows-sign/dist/cjs/sign-with-hook.d.ts b/node_modules/@electron/windows-sign/dist/cjs/sign-with-hook.d.ts new file mode 100644 index 0000000..115fc4a --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sign-with-hook.d.ts @@ -0,0 +1,8 @@ +import { InternalSignOptions } from './types'; +/** + * Sign with a hook function, basically letting everyone + * write completely custom sign logic + * + * @param {InternalSignOptions} options + */ +export declare function signWithHook(options: InternalSignOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/cjs/sign-with-hook.js b/node_modules/@electron/windows-sign/dist/cjs/sign-with-hook.js new file mode 100644 index 0000000..04108bb --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sign-with-hook.js @@ -0,0 +1,45 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.signWithHook = void 0; +const path_1 = __importDefault(require("path")); +const log_1 = require("./utils/log"); +let hookFunction; +function getHookFunction(options) { + if (options.hookFunction) { + return options.hookFunction; + } + if (options.hookModulePath) { + const module = require(path_1.default.resolve(options.hookModulePath)); + if (module.default) { + return module.default; + } + if (typeof module === 'function') { + return module; + } + } + if (!hookFunction) { + throw new Error('No hook function found. Signing will not be possible. Please see the documentation for how to pass a hook function to @electron/windows-sign'); + } + return hookFunction; +} +/** + * Sign with a hook function, basically letting everyone + * write completely custom sign logic + * + * @param {InternalSignOptions} options + */ +async function signWithHook(options) { + hookFunction = getHookFunction(options); + for (const file of options.files) { + try { + await hookFunction(file); + } + catch (error) { + (0, log_1.log)(`Error signing ${file}`, error); + } + } +} +exports.signWithHook = signWithHook; diff --git a/node_modules/@electron/windows-sign/dist/cjs/sign-with-signtool.d.ts b/node_modules/@electron/windows-sign/dist/cjs/sign-with-signtool.d.ts new file mode 100644 index 0000000..3e42c3c --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sign-with-signtool.d.ts @@ -0,0 +1,2 @@ +import { InternalSignOptions } from './types'; +export declare function signWithSignTool(options: InternalSignOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/cjs/sign-with-signtool.js b/node_modules/@electron/windows-sign/dist/cjs/sign-with-signtool.js new file mode 100644 index 0000000..eeab18f --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sign-with-signtool.js @@ -0,0 +1,118 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.signWithSignTool = void 0; +const path_1 = __importDefault(require("path")); +const log_1 = require("./utils/log"); +const spawn_1 = require("./spawn"); +const cross_dirname_1 = require("cross-dirname"); +const DIRNAME = (0, cross_dirname_1.getDirname)(); +function getSigntoolArgs(options) { + // See the following url for docs + // https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe + const { certificateFile, certificatePassword, hash, timestampServer } = options; + const args = ['sign']; + // Automatically select cert + if (options.automaticallySelectCertificate) { + args.push('/a'); + } + // Dual-sign + if (options.appendSignature) { + args.push('/as'); + } + // Timestamp + if (hash === "sha256" /* HASHES.sha256 */) { + args.push('/tr', timestampServer); + args.push('/td', hash); + } + else { + args.push('/t', timestampServer); + } + // Certificate file + if (certificateFile) { + args.push('/f', path_1.default.resolve(certificateFile)); + } + // Certificate password + if (certificatePassword) { + args.push('/p', certificatePassword); + } + // Hash + args.push('/fd', hash); + // Description + if (options.description) { + args.push('/d', options.description); + } + // Website + if (options.website) { + args.push('/du', options.website); + } + // Debug + if (options.debug) { + args.push('/debug'); + } + if (options.signWithParams) { + const extraArgs = []; + if (Array.isArray(options.signWithParams)) { + extraArgs.push(...options.signWithParams); + } + else { + // Split up at spaces and doublequotes + extraArgs.push(...options.signWithParams.match(/(?:[^\s"]+|"[^"]*")+/g)); + } + (0, log_1.log)('Parsed signWithParams as:', extraArgs); + args.push(...extraArgs); + } + return args; +} +async function execute(options) { + const { signToolPath, files } = options; + const args = getSigntoolArgs(options); + (0, log_1.log)('Executing signtool with args', { args, files }); + const { code, stderr, stdout } = await (0, spawn_1.spawnPromise)(signToolPath, [...args, ...files], { + env: process.env, + cwd: process.cwd() + }); + if (code !== 0) { + throw new Error(`Signtool exited with code ${code}. Stderr: ${stderr}. Stdout: ${stdout}`); + } +} +async function signWithSignTool(options) { + const certificatePassword = options.certificatePassword || process.env.WINDOWS_CERTIFICATE_PASSWORD; + const certificateFile = options.certificateFile || process.env.WINDOWS_CERTIFICATE_FILE; + const signWithParams = options.signWithParams || process.env.WINDOWS_SIGN_WITH_PARAMS; + const timestampServer = options.timestampServer || process.env.WINDOWS_TIMESTAMP_SERVER || 'http://timestamp.digicert.com'; + const signToolPath = options.signToolPath || process.env.WINDOWS_SIGNTOOL_PATH || path_1.default.join(DIRNAME, '../../vendor/signtool.exe'); + const description = options.description || process.env.WINDOWS_SIGN_DESCRIPTION; + const website = options.website || process.env.WINDOWS_SIGN_WEBSITE; + if (!certificateFile && !(signWithParams || signToolPath)) { + throw new Error('You must provide a certificateFile and a signToolPath or signing parameters'); + } + if (!signToolPath && !signWithParams && !certificatePassword) { + throw new Error('You must provide a certificatePassword or signing parameters'); + } + const internalOptions = { + appendSignature: false, + ...options, + certificateFile, + certificatePassword, + signWithParams, + signToolPath, + description, + timestampServer, + website + }; + const hashes = options.hashes == null || options.hashes.length === 0 + ? ["sha1" /* HASHES.sha1 */, "sha256" /* HASHES.sha256 */] + : options.hashes; + if (hashes.includes("sha1" /* HASHES.sha1 */)) { + await execute({ ...internalOptions, hash: "sha1" /* HASHES.sha1 */ }); + // If we signed with SHA1, we need to append the SHA256 signature: + internalOptions.appendSignature = true; + } + if (hashes.includes("sha256" /* HASHES.sha256 */)) { + await execute({ ...internalOptions, hash: "sha256" /* HASHES.sha256 */ }); + } +} +exports.signWithSignTool = signWithSignTool; diff --git a/node_modules/@electron/windows-sign/dist/cjs/sign.d.ts b/node_modules/@electron/windows-sign/dist/cjs/sign.d.ts new file mode 100644 index 0000000..3710442 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sign.d.ts @@ -0,0 +1,13 @@ +import { SignOptions } from './types'; +/** + * This is the main function exported from this module. It'll + * look at your options, determine the best way to sign a file, + * and then return one of our internal functions to do the actual + * signing. + * + * @param options + * @returns {Promise} + * + * @category Sign + */ +export declare function sign(options: SignOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/cjs/sign.js b/node_modules/@electron/windows-sign/dist/cjs/sign.js new file mode 100644 index 0000000..58f911e --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/sign.js @@ -0,0 +1,45 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.sign = void 0; +const files_1 = require("./files"); +const sign_with_hook_1 = require("./sign-with-hook"); +const sign_with_signtool_1 = require("./sign-with-signtool"); +const log_1 = require("./utils/log"); +const parse_env_1 = require("./utils/parse-env"); +/** + * This is the main function exported from this module. It'll + * look at your options, determine the best way to sign a file, + * and then return one of our internal functions to do the actual + * signing. + * + * @param options + * @returns {Promise} + * + * @category Sign + */ +async function sign(options) { + const signJavaScript = options.signJavaScript || (0, parse_env_1.booleanFromEnv)('WINDOWS_SIGN_JAVASCRIPT'); + const hookModulePath = options.hookModulePath || process.env.WINDOWS_SIGN_HOOK_MODULE_PATH; + if (options.debug) { + (0, log_1.enableDebugging)(); + } + (0, log_1.log)('Called with options', { options }); + const files = (0, files_1.getFilesToSign)(options); + const internalOptions = { + ...options, + signJavaScript, + hookModulePath, + files + }; + // If a hook is provides, sign with the hook + if (internalOptions.hookFunction || internalOptions.hookModulePath) { + (0, log_1.log)('Signing with hook'); + return (0, sign_with_hook_1.signWithHook)(internalOptions); + } + // If we're going with the defaults, we're signing + // with signtool. Custom signing tools are also + // handled here. + (0, log_1.log)('Signing with signtool'); + return (0, sign_with_signtool_1.signWithSignTool)(internalOptions); +} +exports.sign = sign; diff --git a/node_modules/@electron/windows-sign/dist/cjs/spawn.d.ts b/node_modules/@electron/windows-sign/dist/cjs/spawn.d.ts new file mode 100644 index 0000000..19f6ce8 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/spawn.d.ts @@ -0,0 +1,16 @@ +/// +import { SpawnOptions } from 'child_process'; +export interface SpawnPromiseResult { + stdout: string; + stderr: string; + code: number; +} +/** + * Spawn a process as a promise + * + * @param {string} name + * @param {Array} args + * @param {SpawnOptions} [options] + * @returns {Promise} + */ +export declare function spawnPromise(name: string, args: Array, options?: SpawnOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/cjs/spawn.js b/node_modules/@electron/windows-sign/dist/cjs/spawn.js new file mode 100644 index 0000000..d22f6b6 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/spawn.js @@ -0,0 +1,34 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.spawnPromise = void 0; +const log_1 = require("./utils/log"); +/** + * Spawn a process as a promise + * + * @param {string} name + * @param {Array} args + * @param {SpawnOptions} [options] + * @returns {Promise} + */ +function spawnPromise(name, args, options) { + return new Promise((resolve) => { + const { spawn } = require('child_process'); + const fork = spawn(name, args, options); + (0, log_1.log)(`Spawning ${name} with ${args}`); + let stdout = ''; + let stderr = ''; + fork.stdout.on('data', (data) => { + (0, log_1.log)(`Spawn ${name} stdout: ${data}`); + stdout += data; + }); + fork.stderr.on('data', (data) => { + (0, log_1.log)(`Spawn ${name} stderr: ${data}`); + stderr += data; + }); + fork.on('close', (code) => { + (0, log_1.log)(`Spawn ${name}: Child process exited with code ${code}`); + resolve({ stdout, stderr, code }); + }); + }); +} +exports.spawnPromise = spawnPromise; diff --git a/node_modules/@electron/windows-sign/dist/cjs/types.d.ts b/node_modules/@electron/windows-sign/dist/cjs/types.d.ts new file mode 100644 index 0000000..bebe8d4 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/types.d.ts @@ -0,0 +1,143 @@ +/** + * SHA-1 has been deprecated on Windows since 2016. We'll still dualsign. + * https://social.technet.microsoft.com/wiki/contents/articles/32288.windows-enforcement-of-sha1-certificates.aspx#Post-February_TwentySeventeen_Plan + */ +export declare const enum HASHES { + sha1 = "sha1", + sha256 = "sha256" +} +/** + * Signing can be either by specifying a directory of files to sign. + * + * @category Sign + */ +export type SignOptions = SignOptionsForDirectory | SignOptionsForFiles; +/** + * Options for signing by passing a path to a directory to be codesigned. + * + * @category Sign + */ +export interface SignOptionsForDirectory extends SignToolOptions { + /** + * Path to the application directory. We will scan this + * directory for any `.dll`, `.exe`, `.msi`, or `.node` files and + * codesign them with `signtool.exe`. + */ + appDirectory: string; +} +/** + * Options for signing by passing an array of files to be codesigned. + * + * @category Sign + */ +export interface SignOptionsForFiles extends SignToolOptions { + /** + * Array of paths to files to be codesigned with `signtool.exe`. + */ + files: Array; +} +/** + * @category Utility + */ +export interface SignToolOptions extends OptionalSignToolOptions, OptionalHookOptions { +} +export interface InternalSignOptions extends SignOptionsForFiles { +} +export interface InternalSignToolOptions extends OptionalSignToolOptions, OptionalHookOptions { + signToolPath: string; + timestampServer: string; + files: Array; + hash: HASHES; + appendSignature?: boolean; +} +/** + * @category Utility + */ +export interface OptionalSignToolOptions { + /** + * Path to a `.pfx` code signing certificate. + * Will use `process.env.WINDOWS_CERTIFICATE_FILE` if this option is not provided. + */ + certificateFile?: string; + /** + * Password to {@link certificateFile}. If you don't provide this, + * you need to provide the {@link signWithParams} option. + * Will use `process.env.WINDOWS_CERTIFICATE_PASSWORD` if this option is not provided. + */ + certificatePassword?: string; + /** + * Path to a timestamp server. + * Will use `process.env.WINDOWS_TIMESTAMP_SERVER` if this option is not provided. + * + * @defaultValue http://timestamp.digicert.com + */ + timestampServer?: string; + /** + * Description of the signed content. Will be passed to `signtool.exe` as `/d`. + */ + description?: string; + /** + * URL for the expanded description of the signed content. Will be passed to `signtool.exe` as `/du`. + */ + website?: string; + /** + * Path to the `signtool.exe` used to sign. Will use `vendor/signtool.exe` if not provided. + */ + signToolPath?: string; + /** + * Additional parameters to pass to `signtool.exe`. + * + * @see Microsoft's {@link https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe SignTool.exe documentation} + */ + signWithParams?: string | Array; + /** + * Enables debug logging. + * + * @defaultValue false + */ + debug?: boolean; + /** + * Automatically selects the best signing certificate according to SignTool. Will be passed to `signtool.exe` as `/a`. + * + * @defaultValue true + */ + automaticallySelectCertificate?: boolean; + /** + * Whether or not to sign JavaScript files. + * + * @defaultValue false + */ + signJavaScript?: boolean; + /** + * Hash algorithms to use for signing. + */ + hashes?: HASHES[]; +} +/** + * Custom function that is called sequentially for each file that needs to be signed. + * + * @param fileToSign Absolute path to the file to sign + * + * @category Utility + */ +export type HookFunction = (fileToSign: string) => void | Promise; +/** + * @category Utility + */ +export interface OptionalHookOptions { + /** + * A hook function called for each file that needs to be signed. + * Use this for full control over your app's signing logic. + * `@electron/windows-sign` will not attempt to sign with SignTool if a custom hook is detected. + */ + hookFunction?: HookFunction; + /** + * A path to a JavaScript file, exporting a single function that will be called for each file that needs to be signed. + * Use this for full control over your app's signing logic. + * `@electron/windows-sign` will not attempt to sign with SignTool if a custom hook is detected. + */ + hookModulePath?: string; +} +export interface InternalHookOptions extends OptionalHookOptions { + files: Array; +} diff --git a/node_modules/@electron/windows-sign/dist/cjs/types.js b/node_modules/@electron/windows-sign/dist/cjs/types.js new file mode 100644 index 0000000..c8ad2e5 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/types.js @@ -0,0 +1,2 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); diff --git a/node_modules/@electron/windows-sign/dist/cjs/utils/log.d.ts b/node_modules/@electron/windows-sign/dist/cjs/utils/log.d.ts new file mode 100644 index 0000000..d36710e --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/utils/log.d.ts @@ -0,0 +1,2 @@ +export declare function enableDebugging(): void; +export declare const log: import("debug").Debugger; diff --git a/node_modules/@electron/windows-sign/dist/cjs/utils/log.js b/node_modules/@electron/windows-sign/dist/cjs/utils/log.js new file mode 100644 index 0000000..b927e20 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/utils/log.js @@ -0,0 +1,9 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.log = exports.enableDebugging = void 0; +const debug_1 = require("debug"); +function enableDebugging() { + debug_1.debug.enable('electron-windows-sign'); +} +exports.enableDebugging = enableDebugging; +exports.log = (0, debug_1.debug)('electron-windows-sign'); diff --git a/node_modules/@electron/windows-sign/dist/cjs/utils/parse-env.d.ts b/node_modules/@electron/windows-sign/dist/cjs/utils/parse-env.d.ts new file mode 100644 index 0000000..a575201 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/utils/parse-env.d.ts @@ -0,0 +1,11 @@ +/** + * Tries to parse an process.env string to a boolean. + * Will understand undefined as the default value + * Will understand "false", "False", "fAlse", or "0" as `false` + * Will understand everything else as true + * + * @export + * @param {string} name + * @return {*} {boolean} + */ +export declare function booleanFromEnv(name: string): boolean | undefined; diff --git a/node_modules/@electron/windows-sign/dist/cjs/utils/parse-env.js b/node_modules/@electron/windows-sign/dist/cjs/utils/parse-env.js new file mode 100644 index 0000000..c5a6105 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/cjs/utils/parse-env.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.booleanFromEnv = void 0; +/** + * Tries to parse an process.env string to a boolean. + * Will understand undefined as the default value + * Will understand "false", "False", "fAlse", or "0" as `false` + * Will understand everything else as true + * + * @export + * @param {string} name + * @return {*} {boolean} + */ +function booleanFromEnv(name) { + const value = process.env[name]; + if (value === undefined) { + return undefined; + } + if (value.toLowerCase() === 'false' || value === '0') { + return false; + } + return !!value; +} +exports.booleanFromEnv = booleanFromEnv; diff --git a/node_modules/@electron/windows-sign/dist/esm/files.d.ts b/node_modules/@electron/windows-sign/dist/esm/files.d.ts new file mode 100644 index 0000000..d9bb4dd --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/files.d.ts @@ -0,0 +1,16 @@ +import { SignOptions } from './types'; +/** + * Recursively goes through an entire directory and returns an array + * of full paths for files ot sign. + * + * - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node) + * - Microsoft installers (.msi) + * - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle) + * - Catalog files (.cat) + * - Cabinet files (.cab) + * - Silverlight applications (.xap) + * - Scripts (.vbs, .wsf, .ps1) + * If configured: + * - JavaScript files (.js) + */ +export declare function getFilesToSign(options: SignOptions, dir?: string): Array; diff --git a/node_modules/@electron/windows-sign/dist/esm/files.js b/node_modules/@electron/windows-sign/dist/esm/files.js new file mode 100644 index 0000000..728fe47 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/files.js @@ -0,0 +1,59 @@ +import path from 'path'; +import fs from 'fs-extra'; +const IS_PE_REGEX = /\.(exe|dll|sys|efi|scr|node)$/i; +const IS_MSI_REGEX = /\.msi$/i; +const IS_PACKAGE_REGEX = /\.(appx|appxbundle|msix|msixbundle)$/i; +const IS_CATCAB_REGEX = /\.(cat|cab)$/i; +const IS_SILVERLIGHT_REGEX = /\.xap$/i; +const IS_SCRIPT_REGEX = /\.(vbs|wsf|ps1)$/i; +const IS_JS_REGEX = /\.js$/i; +/** + * Recursively goes through an entire directory and returns an array + * of full paths for files ot sign. + * + * - Portable executable files (.exe, .dll, .sys, .efi, .scr, .node) + * - Microsoft installers (.msi) + * - APPX/MSIX packages (.appx, .appxbundle, .msix, .msixbundle) + * - Catalog files (.cat) + * - Cabinet files (.cab) + * - Silverlight applications (.xap) + * - Scripts (.vbs, .wsf, .ps1) + * If configured: + * - JavaScript files (.js) + */ +export function getFilesToSign(options, dir) { + if (isSignOptionsForFiles(options)) { + return options.files; + } + dir = dir || options.appDirectory; + // Array of file paths to sign + const result = []; + // Iterate over the app directory, looking for files to sign + const files = fs.readdirSync(dir); + const regexes = [ + IS_PE_REGEX, + IS_MSI_REGEX, + IS_PACKAGE_REGEX, + IS_CATCAB_REGEX, + IS_SILVERLIGHT_REGEX, + IS_SCRIPT_REGEX + ]; + if (options.signJavaScript) { + regexes.push(IS_JS_REGEX); + } + for (const file of files) { + const fullPath = path.resolve(dir, file); + if (fs.statSync(fullPath).isDirectory()) { + // If it's a directory, recurse + result.push(...getFilesToSign(options, fullPath)); + } + else if (regexes.some((regex) => regex.test(file))) { + // If it's a match, add it to the list + result.push(fullPath); + } + } + return result; +} +function isSignOptionsForFiles(input) { + return !!input.files; +} diff --git a/node_modules/@electron/windows-sign/dist/esm/index.d.ts b/node_modules/@electron/windows-sign/dist/esm/index.d.ts new file mode 100644 index 0000000..6add955 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/index.d.ts @@ -0,0 +1,4 @@ +import { sign } from './sign'; +import { createSeaSignTool, SeaOptions, InternalSeaOptions } from './sea'; +import { HookFunction, OptionalHookOptions, OptionalSignToolOptions, SignOptions, SignToolOptions, SignOptionsForDirectory, SignOptionsForFiles } from './types'; +export { sign, SignOptions, SignToolOptions, HookFunction, OptionalSignToolOptions, OptionalHookOptions, createSeaSignTool, SeaOptions, InternalSeaOptions, SignOptionsForDirectory, SignOptionsForFiles }; diff --git a/node_modules/@electron/windows-sign/dist/esm/index.js b/node_modules/@electron/windows-sign/dist/esm/index.js new file mode 100644 index 0000000..de71781 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/index.js @@ -0,0 +1,3 @@ +import { sign } from './sign'; +import { createSeaSignTool } from './sea'; +export { sign, createSeaSignTool }; diff --git a/node_modules/@electron/windows-sign/dist/esm/sea.d.ts b/node_modules/@electron/windows-sign/dist/esm/sea.d.ts new file mode 100644 index 0000000..ac11ce0 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sea.d.ts @@ -0,0 +1,61 @@ +import { SignToolOptions } from './types'; +/** + * Options for signing with a Node.js single executable application. + * + * @category Single executable applications + */ +export interface SeaOptions { + /** + * Full path to the Node.js single executable application. Needs to end with `.exe`. + */ + path: string; + /** + * A binary to use. Will use the current executable (process.execPath) by default. + * + * @defaultValue The Node.js {@link https://nodejs.org/api/process.html#processexecpath | `process.execPath`} + */ + bin?: string; + /** + * Options to pass to SignTool. + */ + windowsSign: SignToolOptions; +} +/** + * This interface represents {@link SeaOptions} with all optional properties + * inferred by `@electron/windows-sign` if not passed in by the user. + * + * @category Single executable applications + */ +export interface InternalSeaOptions extends Required { + /** + * Directory of the Node.js single executable application. + */ + dir: string; + /** + * File name of the Node.js single executable application. + */ + filename: string; +} +/** + * cross-dir uses new Error() stacks + * to figure out our directory in a way + * that's somewhat cross-compatible. + * + * We can't just use __dirname because it's + * undefined in ESM - and we can't use import.meta.url + * because TypeScript won't allow usage unless you're + * _only_ compiling for ESM. + */ +export declare const DIRNAME: string; +/** + * Uses Node's "Single Executable App" functionality + * to create a Node-driven signtool.exe that calls this + * module. + * + * This is useful with other tooling that _always_ calls + * a signtool.exe to sign. Some of those tools cannot be + * easily configured, but we _can_ override their signtool.exe. + * + * @category Single executable applications + */ +export declare function createSeaSignTool(options?: Partial): Promise; diff --git a/node_modules/@electron/windows-sign/dist/esm/sea.js b/node_modules/@electron/windows-sign/dist/esm/sea.js new file mode 100644 index 0000000..0f0c49d --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sea.js @@ -0,0 +1,213 @@ +import { getDirname } from 'cross-dirname'; +import path from 'path'; +import os from 'os'; +import fs from 'fs-extra'; +import postject from 'postject'; +import { spawnPromise } from './spawn'; +import { log } from './utils/log'; +/** + * cross-dir uses new Error() stacks + * to figure out our directory in a way + * that's somewhat cross-compatible. + * + * We can't just use __dirname because it's + * undefined in ESM - and we can't use import.meta.url + * because TypeScript won't allow usage unless you're + * _only_ compiling for ESM. + */ +export const DIRNAME = getDirname(); +const FILENAMES = { + SEA_CONFIG: 'sea-config.json', + SEA_MAIN: 'sea.js', + SEA_BLOB: 'sea.blob', + SEA_RECEIVER: 'receiver.mjs' +}; +const SEA_MAIN_SCRIPT = ` +const bin = "%PATH_TO_BIN%"; +const script = "%PATH_TO_SCRIPT%"; +const options = %WINDOWS_SIGN_OPTIONS% + +const { spawnSync } = require('child_process'); + +function main() { + console.log("@electron/windows-sign sea"); + console.log({ bin, script }); + + try { + const spawn = spawnSync( + bin, + [ script, JSON.stringify(options), JSON.stringify(process.argv.slice(1)) ], + { stdio: ['inherit', 'inherit', 'pipe'] } + ); + + if (spawn.status !== 0) { + throw new Error(\`Spawn failed with code: \${spawn.status}. Stderr: \${spawn.stderr}\`); + } + } catch (error) { + // Do not rethrow the error or write it to stdout/stderr. Then the process won't terminate. + // See: https://github.com/electron/windows-sign/pull/48 + process.exit(1); + } +} + +main(); +`; +const SEA_RECEIVER_SCRIPT = ` +import { sign } from '@electron/windows-sign'; +import fs from 'fs-extra'; +import path from 'path'; + +const logPath = path.join('electron-windows-sign.log'); +const options = JSON.parse(process.argv[2]); +const signArgv = JSON.parse(process.argv[3]); +const files = signArgv.slice(-1); + +try { + fs.appendFileSync(logPath, \`\\nCalled with: \${JSON.stringify(process.argv, null, 2)}\`); + + sign({ ...options, files }) + .then((result) => { + fs.appendFileSync(logPath, \`\\nSuccessfully signed with result: \${result}\`); + }) + .catch((error) => { + fs.appendFileSync(logPath, \`\\nError from sign: \${error}\`); + throw new Error(error); + }); +} catch (error) { + fs.appendFileSync(logPath, \`\\Error invoking sign: \${error}\`); + throw new Error(error); +} +`; +/** + * Uses Node's "Single Executable App" functionality + * to create a Node-driven signtool.exe that calls this + * module. + * + * This is useful with other tooling that _always_ calls + * a signtool.exe to sign. Some of those tools cannot be + * easily configured, but we _can_ override their signtool.exe. + * + * @category Single executable applications + */ +export async function createSeaSignTool(options = {}) { + checkCompatibility(); + const requiredOptions = await getOptions(options); + await createFiles(requiredOptions); + await createBlob(requiredOptions); + await createBinary(requiredOptions); + await createSeaReceiver(requiredOptions); + await cleanup(requiredOptions); + return requiredOptions; +} +async function createSeaReceiver(options) { + const receiverPath = path.join(options.dir, FILENAMES.SEA_RECEIVER); + await fs.ensureFile(receiverPath); + await fs.writeFile(receiverPath, SEA_RECEIVER_SCRIPT); +} +async function createFiles(options) { + const { dir, bin } = options; + const receiverPath = path.join(options.dir, FILENAMES.SEA_RECEIVER); + // sea-config.json + await fs.outputJSON(path.join(dir, FILENAMES.SEA_CONFIG), { + main: FILENAMES.SEA_MAIN, + output: FILENAMES.SEA_BLOB, + disableExperimentalSEAWarning: true + }, { + spaces: 2 + }); + // signtool.js + const binPath = bin || process.execPath; + const script = SEA_MAIN_SCRIPT + .replace('%PATH_TO_BIN%', escapeMaybe(binPath)) + .replace('%PATH_TO_SCRIPT%', escapeMaybe(receiverPath)) + .replace('%WINDOWS_SIGN_OPTIONS%', JSON.stringify(options.windowsSign)); + await fs.outputFile(path.join(dir, FILENAMES.SEA_MAIN), script); +} +async function createBlob(options) { + const args = ['--experimental-sea-config', 'sea-config.json']; + const bin = process.execPath; + const cwd = options.dir; + log(`Calling ${bin} with options:`, args); + const { stderr, stdout } = await spawnPromise(bin, args, { + cwd + }); + log('stdout:', stdout); + log('stderr:', stderr); +} +async function createBinary(options) { + const { dir, filename } = options; + log(`Creating ${filename} in ${dir}`); + // Copy Node over + const seaPath = path.join(dir, filename); + await fs.copyFile(process.execPath, seaPath); + // Remove the Node signature + const signtool = path.join(DIRNAME, '../../vendor/signtool.exe'); + await spawnPromise(signtool, [ + 'remove', + '/s', + seaPath + ]); + // Inject the blob + const blob = await fs.readFile(path.join(dir, FILENAMES.SEA_BLOB)); + await postject.inject(seaPath, 'NODE_SEA_BLOB', blob, { + sentinelFuse: 'NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2' + }); +} +async function cleanup(options) { + const { dir } = options; + const toRemove = [ + FILENAMES.SEA_BLOB, + FILENAMES.SEA_MAIN, + FILENAMES.SEA_CONFIG + ]; + for (const file of toRemove) { + try { + await fs.remove(path.join(dir, file)); + } + catch (error) { + console.warn(`Tried and failed to remove ${file}. Continuing.`, error); + } + } +} +async function getOptions(options) { + const cloned = { ...options }; + if (!cloned.path) { + cloned.path = path.join(os.homedir(), '.electron', 'windows-sign', 'sea.exe'); + await fs.ensureFile(cloned.path); + } + if (!cloned.bin) { + cloned.bin = process.execPath; + } + if (!cloned.windowsSign) { + throw new Error('Did not find windowsSign options, which are required'); + } + return { + path: cloned.path, + dir: path.dirname(cloned.path), + filename: path.basename(cloned.path), + bin: cloned.bin, + windowsSign: cloned.windowsSign + }; +} +/** + * Ensures that the current Node.js version supports SEA app generation and errors if not. + */ +function checkCompatibility() { + const version = process.versions.node; + const split = version.split('.'); + const major = parseInt(split[0], 10); + if (major >= 20) { + return true; + } + throw new Error(`Your Node.js version (${process.version}) does not support Single Executable Applications. Please upgrade your version of Node.js.`); +} +/** Make sure that the input string has escaped backwards slashes + * - but never double-escaped backwards slashes. + */ +function escapeMaybe(input) { + const result = input.split(path.sep).join('\\\\'); + if (result.includes('\\\\\\\\')) { + throw new Error(`Your passed input ${input} contains escaped slashes. Please do not escape them`); + } + return result; +} diff --git a/node_modules/@electron/windows-sign/dist/esm/sign-with-hook.d.ts b/node_modules/@electron/windows-sign/dist/esm/sign-with-hook.d.ts new file mode 100644 index 0000000..115fc4a --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sign-with-hook.d.ts @@ -0,0 +1,8 @@ +import { InternalSignOptions } from './types'; +/** + * Sign with a hook function, basically letting everyone + * write completely custom sign logic + * + * @param {InternalSignOptions} options + */ +export declare function signWithHook(options: InternalSignOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/esm/sign-with-hook.js b/node_modules/@electron/windows-sign/dist/esm/sign-with-hook.js new file mode 100644 index 0000000..d38d9fe --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sign-with-hook.js @@ -0,0 +1,38 @@ +import path from 'path'; +import { log } from './utils/log'; +let hookFunction; +function getHookFunction(options) { + if (options.hookFunction) { + return options.hookFunction; + } + if (options.hookModulePath) { + const module = require(path.resolve(options.hookModulePath)); + if (module.default) { + return module.default; + } + if (typeof module === 'function') { + return module; + } + } + if (!hookFunction) { + throw new Error('No hook function found. Signing will not be possible. Please see the documentation for how to pass a hook function to @electron/windows-sign'); + } + return hookFunction; +} +/** + * Sign with a hook function, basically letting everyone + * write completely custom sign logic + * + * @param {InternalSignOptions} options + */ +export async function signWithHook(options) { + hookFunction = getHookFunction(options); + for (const file of options.files) { + try { + await hookFunction(file); + } + catch (error) { + log(`Error signing ${file}`, error); + } + } +} diff --git a/node_modules/@electron/windows-sign/dist/esm/sign-with-signtool.d.ts b/node_modules/@electron/windows-sign/dist/esm/sign-with-signtool.d.ts new file mode 100644 index 0000000..3e42c3c --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sign-with-signtool.d.ts @@ -0,0 +1,2 @@ +import { InternalSignOptions } from './types'; +export declare function signWithSignTool(options: InternalSignOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/esm/sign-with-signtool.js b/node_modules/@electron/windows-sign/dist/esm/sign-with-signtool.js new file mode 100644 index 0000000..1b27599 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sign-with-signtool.js @@ -0,0 +1,111 @@ +import path from 'path'; +import { log } from './utils/log'; +import { spawnPromise } from './spawn'; +import { getDirname } from 'cross-dirname'; +const DIRNAME = getDirname(); +function getSigntoolArgs(options) { + // See the following url for docs + // https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe + const { certificateFile, certificatePassword, hash, timestampServer } = options; + const args = ['sign']; + // Automatically select cert + if (options.automaticallySelectCertificate) { + args.push('/a'); + } + // Dual-sign + if (options.appendSignature) { + args.push('/as'); + } + // Timestamp + if (hash === "sha256" /* HASHES.sha256 */) { + args.push('/tr', timestampServer); + args.push('/td', hash); + } + else { + args.push('/t', timestampServer); + } + // Certificate file + if (certificateFile) { + args.push('/f', path.resolve(certificateFile)); + } + // Certificate password + if (certificatePassword) { + args.push('/p', certificatePassword); + } + // Hash + args.push('/fd', hash); + // Description + if (options.description) { + args.push('/d', options.description); + } + // Website + if (options.website) { + args.push('/du', options.website); + } + // Debug + if (options.debug) { + args.push('/debug'); + } + if (options.signWithParams) { + const extraArgs = []; + if (Array.isArray(options.signWithParams)) { + extraArgs.push(...options.signWithParams); + } + else { + // Split up at spaces and doublequotes + extraArgs.push(...options.signWithParams.match(/(?:[^\s"]+|"[^"]*")+/g)); + } + log('Parsed signWithParams as:', extraArgs); + args.push(...extraArgs); + } + return args; +} +async function execute(options) { + const { signToolPath, files } = options; + const args = getSigntoolArgs(options); + log('Executing signtool with args', { args, files }); + const { code, stderr, stdout } = await spawnPromise(signToolPath, [...args, ...files], { + env: process.env, + cwd: process.cwd() + }); + if (code !== 0) { + throw new Error(`Signtool exited with code ${code}. Stderr: ${stderr}. Stdout: ${stdout}`); + } +} +export async function signWithSignTool(options) { + const certificatePassword = options.certificatePassword || process.env.WINDOWS_CERTIFICATE_PASSWORD; + const certificateFile = options.certificateFile || process.env.WINDOWS_CERTIFICATE_FILE; + const signWithParams = options.signWithParams || process.env.WINDOWS_SIGN_WITH_PARAMS; + const timestampServer = options.timestampServer || process.env.WINDOWS_TIMESTAMP_SERVER || 'http://timestamp.digicert.com'; + const signToolPath = options.signToolPath || process.env.WINDOWS_SIGNTOOL_PATH || path.join(DIRNAME, '../../vendor/signtool.exe'); + const description = options.description || process.env.WINDOWS_SIGN_DESCRIPTION; + const website = options.website || process.env.WINDOWS_SIGN_WEBSITE; + if (!certificateFile && !(signWithParams || signToolPath)) { + throw new Error('You must provide a certificateFile and a signToolPath or signing parameters'); + } + if (!signToolPath && !signWithParams && !certificatePassword) { + throw new Error('You must provide a certificatePassword or signing parameters'); + } + const internalOptions = { + appendSignature: false, + ...options, + certificateFile, + certificatePassword, + signWithParams, + signToolPath, + description, + timestampServer, + website + }; + const hashes = options.hashes == null || options.hashes.length === 0 + ? ["sha1" /* HASHES.sha1 */, "sha256" /* HASHES.sha256 */] + : options.hashes; + if (hashes.includes("sha1" /* HASHES.sha1 */)) { + await execute({ ...internalOptions, hash: "sha1" /* HASHES.sha1 */ }); + // If we signed with SHA1, we need to append the SHA256 signature: + internalOptions.appendSignature = true; + } + if (hashes.includes("sha256" /* HASHES.sha256 */)) { + await execute({ ...internalOptions, hash: "sha256" /* HASHES.sha256 */ }); + } +} diff --git a/node_modules/@electron/windows-sign/dist/esm/sign.d.ts b/node_modules/@electron/windows-sign/dist/esm/sign.d.ts new file mode 100644 index 0000000..3710442 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sign.d.ts @@ -0,0 +1,13 @@ +import { SignOptions } from './types'; +/** + * This is the main function exported from this module. It'll + * look at your options, determine the best way to sign a file, + * and then return one of our internal functions to do the actual + * signing. + * + * @param options + * @returns {Promise} + * + * @category Sign + */ +export declare function sign(options: SignOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/esm/sign.js b/node_modules/@electron/windows-sign/dist/esm/sign.js new file mode 100644 index 0000000..86ef48a --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/sign.js @@ -0,0 +1,41 @@ +import { getFilesToSign } from './files'; +import { signWithHook } from './sign-with-hook'; +import { signWithSignTool } from './sign-with-signtool'; +import { enableDebugging, log } from './utils/log'; +import { booleanFromEnv } from './utils/parse-env'; +/** + * This is the main function exported from this module. It'll + * look at your options, determine the best way to sign a file, + * and then return one of our internal functions to do the actual + * signing. + * + * @param options + * @returns {Promise} + * + * @category Sign + */ +export async function sign(options) { + const signJavaScript = options.signJavaScript || booleanFromEnv('WINDOWS_SIGN_JAVASCRIPT'); + const hookModulePath = options.hookModulePath || process.env.WINDOWS_SIGN_HOOK_MODULE_PATH; + if (options.debug) { + enableDebugging(); + } + log('Called with options', { options }); + const files = getFilesToSign(options); + const internalOptions = { + ...options, + signJavaScript, + hookModulePath, + files + }; + // If a hook is provides, sign with the hook + if (internalOptions.hookFunction || internalOptions.hookModulePath) { + log('Signing with hook'); + return signWithHook(internalOptions); + } + // If we're going with the defaults, we're signing + // with signtool. Custom signing tools are also + // handled here. + log('Signing with signtool'); + return signWithSignTool(internalOptions); +} diff --git a/node_modules/@electron/windows-sign/dist/esm/spawn.d.ts b/node_modules/@electron/windows-sign/dist/esm/spawn.d.ts new file mode 100644 index 0000000..19f6ce8 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/spawn.d.ts @@ -0,0 +1,16 @@ +/// +import { SpawnOptions } from 'child_process'; +export interface SpawnPromiseResult { + stdout: string; + stderr: string; + code: number; +} +/** + * Spawn a process as a promise + * + * @param {string} name + * @param {Array} args + * @param {SpawnOptions} [options] + * @returns {Promise} + */ +export declare function spawnPromise(name: string, args: Array, options?: SpawnOptions): Promise; diff --git a/node_modules/@electron/windows-sign/dist/esm/spawn.js b/node_modules/@electron/windows-sign/dist/esm/spawn.js new file mode 100644 index 0000000..0824325 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/spawn.js @@ -0,0 +1,30 @@ +import { log } from './utils/log'; +/** + * Spawn a process as a promise + * + * @param {string} name + * @param {Array} args + * @param {SpawnOptions} [options] + * @returns {Promise} + */ +export function spawnPromise(name, args, options) { + return new Promise((resolve) => { + const { spawn } = require('child_process'); + const fork = spawn(name, args, options); + log(`Spawning ${name} with ${args}`); + let stdout = ''; + let stderr = ''; + fork.stdout.on('data', (data) => { + log(`Spawn ${name} stdout: ${data}`); + stdout += data; + }); + fork.stderr.on('data', (data) => { + log(`Spawn ${name} stderr: ${data}`); + stderr += data; + }); + fork.on('close', (code) => { + log(`Spawn ${name}: Child process exited with code ${code}`); + resolve({ stdout, stderr, code }); + }); + }); +} diff --git a/node_modules/@electron/windows-sign/dist/esm/types.d.ts b/node_modules/@electron/windows-sign/dist/esm/types.d.ts new file mode 100644 index 0000000..bebe8d4 --- /dev/null +++ b/node_modules/@electron/windows-sign/dist/esm/types.d.ts @@ -0,0 +1,143 @@ +/** + * SHA-1 has been deprecated on Windows since 2016. We'll still dualsign. + * https://social.technet.microsoft.com/wiki/contents/articles/32288.windows-enforcement-of-sha1-certificates.aspx#Post-February_TwentySeventeen_Plan + */ +export declare const enum HASHES { + sha1 = "sha1", + sha256 = "sha256" +} +/** + * Signing can be either by specifying a directory of files to sign. + * + * @category Sign + */ +export type SignOptions = SignOptionsForDirectory | SignOptionsForFiles; +/** + * Options for signing by passing a path to a directory to be codesigned. + * + * @category Sign + */ +export interface SignOptionsForDirectory extends SignToolOptions { + /** + * Path to the application directory. We will scan this + * directory for any `.dll`, `.exe`, `.msi`, or `.node` files and + * codesign them with `signtool.exe`. + */ + appDirectory: string; +} +/** + * Options for signing by passing an array of files to be codesigned. + * + * @category Sign + */ +export interface SignOptionsForFiles extends SignToolOptions { + /** + * Array of paths to files to be codesigned with `signtool.exe`. + */ + files: Array; +} +/** + * @category Utility + */ +export interface SignToolOptions extends OptionalSignToolOptions, OptionalHookOptions { +} +export interface InternalSignOptions extends SignOptionsForFiles { +} +export interface InternalSignToolOptions extends OptionalSignToolOptions, OptionalHookOptions { + signToolPath: string; + timestampServer: string; + files: Array; + hash: HASHES; + appendSignature?: boolean; +} +/** + * @category Utility + */ +export interface OptionalSignToolOptions { + /** + * Path to a `.pfx` code signing certificate. + * Will use `process.env.WINDOWS_CERTIFICATE_FILE` if this option is not provided. + */ + certificateFile?: string; + /** + * Password to {@link certificateFile}. If you don't provide this, + * you need to provide the {@link signWithParams} option. + * Will use `process.env.WINDOWS_CERTIFICATE_PASSWORD` if this option is not provided. + */ + certificatePassword?: string; + /** + * Path to a timestamp server. + * Will use `process.env.WINDOWS_TIMESTAMP_SERVER` if this option is not provided. + * + * @defaultValue http://timestamp.digicert.com + */ + timestampServer?: string; + /** + * Description of the signed content. Will be passed to `signtool.exe` as `/d`. + */ + description?: string; + /** + * URL for the expanded description of the signed content. Will be passed to `signtool.exe` as `/du`. + */ + website?: string; + /** + * Path to the `signtool.exe` used to sign. Will use `vendor/signtool.exe` if not provided. + */ + signToolPath?: string; + /** + * Additional parameters to pass to `signtool.exe`. + * + * @see Microsoft's {@link https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe SignTool.exe documentation} + */ + signWithParams?: string | Array; + /** + * Enables debug logging. + * + * @defaultValue false + */ + debug?: boolean; + /** + * Automatically selects the best signing certificate according to SignTool. Will be passed to `signtool.exe` as `/a`. + * + * @defaultValue true + */ + automaticallySelectCertificate?: boolean; + /** + * Whether or not to sign JavaScript files. + * + * @defaultValue false + */ + signJavaScript?: boolean; + /** + * Hash algorithms to use for signing. + */ + hashes?: HASHES[]; +} +/** + * Custom function that is called sequentially for each file that needs to be signed. + * + * @param fileToSign Absolute path to the file to sign + * + * @category Utility + */ +export type HookFunction = (fileToSign: string) => void | Promise; +/** + * @category Utility + */ +export interface OptionalHookOptions { + /** + * A hook function called for each file that needs to be signed. + * Use this for full control over your app's signing logic. + * `@electron/windows-sign` will not attempt to sign with SignTool if a custom hook is detected. + */ + hookFunction?: HookFunction; + /** + * A path to a JavaScript file, exporting a single function that will be called for each file that needs to be signed. + * Use this for full control over your app's signing logic. + * `@electron/windows-sign` will not attempt to sign with SignTool if a custom hook is detected. + */ + hookModulePath?: string; +} +export interface InternalHookOptions extends OptionalHookOptions { + files: Array; +}