diff options
Diffstat (limited to 'desktop/node_modules/electron-packager/src')
16 files changed, 2595 insertions, 0 deletions
diff --git a/desktop/node_modules/electron-packager/src/cli.js b/desktop/node_modules/electron-packager/src/cli.js new file mode 100644 index 0000000..29f911d --- /dev/null +++ b/desktop/node_modules/electron-packager/src/cli.js @@ -0,0 +1,143 @@ +'use strict' + +const { info, hostInfo, warning } = require('./common') +const fs = require('fs-extra') +const { initializeProxy } = require('@electron/get') +const packager = require('..') +const path = require('path') +const yargs = require('yargs-parser') + +/* istanbul ignore next */ +async function printUsageAndExit (isError) { + const usage = (await fs.readFile(path.resolve(__dirname, '..', 'usage.txt'))).toString() + const print = isError ? console.error : console.log + print(usage) + process.exit(isError ? 1 : 0) +} + +module.exports = { + parseArgs: function parseArgs (argv) { + const args = yargs(argv, { + boolean: [ + 'all', + 'deref-symlinks', + 'download.rejectUnauthorized', + 'junk', + 'overwrite', + 'prune', + 'quiet' + ], + default: { + 'deref-symlinks': true, + 'download.rejectUnauthorized': true, + junk: true, + prune: true + }, + string: [ + 'electron-version', + 'out' + ] + }) + + args.dir = args._[0] + args.name = args._[1] + + const protocolSchemes = [].concat(args.protocol || []) + const protocolNames = [].concat(args.protocolName || []) + + if (protocolSchemes && protocolNames && protocolNames.length === protocolSchemes.length) { + args.protocols = protocolSchemes.map(function (scheme, i) { + return { schemes: [scheme], name: protocolNames[i] } + }) + } + + if (args.out === '') { + warning('Specifying --out= without a value is the same as the default value', args.quiet) + args.out = null + } + + // Overrides for multi-typed arguments, because minimist doesn't support it + + // asar: `Object` or `true` + if (args.asar === 'true' || args.asar instanceof Array) { + warning('--asar does not take any arguments, it only has sub-properties (see --help)', args.quiet) + args.asar = true + } + + // 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) + args.osxSign = true + } else if (typeof args['osx-sign'] === 'object') { + if (Array.isArray(args['osx-sign'])) { + warning('Remove --osx-sign (the bare flag) from the command line, only specify sub-properties (see --help)', args.quiet) + } else { + // Keep kebab case of sub properties + args.osxSign = args['osx-sign'] + } + } + + if (args.osxNotarize) { + let notarize = true + if (typeof args.osxNotarize !== 'object' || Array.isArray(args.osxNotarize)) { + warning('--osx-notarize does not take any arguments, it only has sub-properties (see --help)', args.quiet) + notarize = false + } else if (!args.osxSign) { + warning('Notarization was enabled but macOS code signing was not, code signing is a requirement for notarization, notarize will not run', args.quiet) + notarize = false + } + + if (!notarize) { + args.osxNotarize = null + } + } + + // tmpdir: `String` or `false` + if (args.tmpdir === 'false') { + warning('--tmpdir=false is deprecated, use --no-tmpdir instead', args.quiet) + args.tmpdir = false + } + + return args + }, + run: /* istanbul ignore next */ async function run (argv) { + const args = module.exports.parseArgs(argv) + + // temporary fix for https://github.com/nodejs/node/issues/6456 + for (const stdioWriter of [process.stdout, process.stderr]) { + if (stdioWriter._handle && stdioWriter._handle.setBlocking) { + stdioWriter._handle.setBlocking(true) + } + } + + if (args.help) { + await printUsageAndExit(false) + } else if (args.version) { + if (typeof args.version !== 'boolean') { + console.error('--version does not take an argument. Perhaps you meant --app-version or --electron-version?\n') + } + console.log(hostInfo()) + process.exit(0) + } else if (!args.dir) { + await printUsageAndExit(true) + } + + initializeProxy() + + try { + const appPaths = await packager(args) + if (appPaths.length > 1) { + info(`Wrote new apps to:\n${appPaths.join('\n')}`, args.quiet) + } else if (appPaths.length === 1) { + info(`Wrote new app to: ${appPaths[0]}`, args.quiet) + } + } catch (err) { + if (err.message) { + console.error(err.message) + } else { + console.error(err, err.stack) + } + process.exit(1) + } + } +} diff --git a/desktop/node_modules/electron-packager/src/common.js b/desktop/node_modules/electron-packager/src/common.js new file mode 100644 index 0000000..1f6111f --- /dev/null +++ b/desktop/node_modules/electron-packager/src/common.js @@ -0,0 +1,128 @@ +'use strict' + +const debug = require('debug')('electron-packager') +const filenamify = require('filenamify') +const fs = require('fs-extra') +const metadata = require('../package.json') +const os = require('os') +const path = require('path') + +function sanitizeAppName (name) { + return filenamify(name, { replacement: '-' }) +} + +function generateFinalBasename (opts) { + return `${sanitizeAppName(opts.name)}-${opts.platform}-${opts.arch}` +} + +function generateFinalPath (opts) { + return path.join(opts.out || process.cwd(), generateFinalBasename(opts)) +} + +function info (message, quiet) { + if (!quiet) { + console.error(message) + } +} + +function warning (message, quiet) { + if (!quiet) { + console.warn(`WARNING: ${message}`) + } +} + +function subOptionWarning (properties, optionName, parameter, value, quiet) { + if (Object.prototype.hasOwnProperty.call(properties, parameter)) { + warning(`${optionName}.${parameter} will be inferred from the main options`, quiet) + } + properties[parameter] = value +} + +function createAsarOpts (opts) { + let asarOptions + if (opts.asar === true) { + asarOptions = {} + } else if (typeof opts.asar === 'object') { + asarOptions = opts.asar + } else if (opts.asar === false || opts.asar === undefined) { + return false + } else { + warning(`asar parameter set to an invalid value (${opts.asar}), ignoring and disabling asar`, opts.quiet) + return false + } + + return asarOptions +} + +module.exports = { + ensureArray: function ensureArray (value) { + return Array.isArray(value) ? value : [value] + }, + isPlatformMac: function isPlatformMac (platform) { + return platform === 'darwin' || platform === 'mas' + }, + + createAsarOpts: createAsarOpts, + + deprecatedParameter: function deprecatedParameter (properties, oldName, newName, newCLIName, quiet) { + if (Object.prototype.hasOwnProperty.call(properties, oldName)) { + warning(`The ${oldName} parameter is deprecated, use ${newName} (or --${newCLIName} in the CLI) instead`, quiet) + if (!Object.prototype.hasOwnProperty.call(properties, newName)) { + properties[newName] = properties[oldName] + } + delete properties[oldName] + } + }, + subOptionWarning: subOptionWarning, + + baseTempDir: function baseTempDir (opts) { + return path.join(opts.tmpdir || os.tmpdir(), 'electron-packager') + }, + generateFinalBasename: generateFinalBasename, + generateFinalPath: generateFinalPath, + sanitizeAppName, + /** + * Convert slashes to UNIX-format separators. + */ + normalizePath: function normalizePath (pathToNormalize) { + return pathToNormalize.replace(/\\/g, '/') + }, + /** + * Validates that the application directory contains a package.json file, and that there exists an + * appropriate main entry point file, per the rules of the "main" field in package.json. + * + * See: https://docs.npmjs.com/cli/v6/configuring-npm/package-json#main + * + * @param appDir - the directory specified by the user + * @param bundledAppDir - the directory where the appDir is copied to in the bundled Electron app + */ + validateElectronApp: async function validateElectronApp (appDir, bundledAppDir) { + debug('Validating bundled Electron app') + debug('Checking for a package.json file') + + const bundledPackageJSONPath = path.join(bundledAppDir, 'package.json') + if (!(await fs.pathExists(bundledPackageJSONPath))) { + const originalPackageJSONPath = path.join(appDir, 'package.json') + throw new Error(`Application manifest was not found. Make sure "${originalPackageJSONPath}" exists and does not get ignored by your ignore option`) + } + + debug('Checking for the main entry point file') + const packageJSON = await fs.readJson(bundledPackageJSONPath) + const mainScriptBasename = packageJSON.main || 'index.js' + const mainScript = path.resolve(bundledAppDir, mainScriptBasename) + if (!(await fs.pathExists(mainScript))) { + const originalMainScript = path.join(appDir, mainScriptBasename) + throw new Error(`The main entry point to your app was not found. Make sure "${originalMainScript}" exists and does not get ignored by your ignore option`) + } + + debug('Validation complete') + }, + + hostInfo: function hostInfo () { + return `Electron Packager ${metadata.version}\n` + + `Node ${process.version}\n` + + `Host Operating system: ${process.platform} ${os.release()} (${process.arch})` + }, + info: info, + warning: warning +} diff --git a/desktop/node_modules/electron-packager/src/copy-filter.js b/desktop/node_modules/electron-packager/src/copy-filter.js new file mode 100644 index 0000000..ddfc608 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/copy-filter.js @@ -0,0 +1,110 @@ +'use strict' + +const common = require('./common') +const debug = require('debug')('electron-packager') +const junk = require('junk') +const path = require('path') +const prune = require('./prune') +const targets = require('./targets') + +const DEFAULT_IGNORES = [ + '/package-lock\\.json$', + '/yarn\\.lock$', + '/\\.git($|/)', + '/node_modules/\\.bin($|/)', + '\\.o(bj)?$', + '/node_gyp_bins($|/)' +] + +function populateIgnoredPaths (opts) { + opts.originalIgnore = opts.ignore + if (typeof (opts.ignore) !== 'function') { + if (opts.ignore) { + opts.ignore = common.ensureArray(opts.ignore).concat(DEFAULT_IGNORES) + } else { + opts.ignore = [].concat(DEFAULT_IGNORES) + } + if (process.platform === 'linux') { + opts.ignore.push(common.baseTempDir(opts)) + } + + debug('Ignored path regular expressions:', opts.ignore) + } +} + +function generateIgnoredOutDirs (opts) { + const normalizedOut = opts.out ? path.resolve(opts.out) : null + const ignoredOutDirs = [] + if (normalizedOut === null || normalizedOut === process.cwd()) { + for (const [platform, archs] of Object.entries(targets.officialPlatformArchCombos)) { + for (const arch of archs) { + const basenameOpts = { + arch: arch, + name: opts.name, + platform: platform + } + ignoredOutDirs.push(path.join(process.cwd(), common.generateFinalBasename(basenameOpts))) + } + } + } else { + ignoredOutDirs.push(normalizedOut) + } + + debug('Ignored paths based on the out param:', ignoredOutDirs) + + return ignoredOutDirs +} + +function generateFilterFunction (ignore) { + if (typeof (ignore) === 'function') { + return file => !ignore(file) + } else { + const ignoredRegexes = common.ensureArray(ignore) + + return function filterByRegexes (file) { + return !ignoredRegexes.some(regex => file.match(regex)) + } + } +} + +function userPathFilter (opts) { + const filterFunc = generateFilterFunction(opts.ignore || []) + const ignoredOutDirs = generateIgnoredOutDirs(opts) + const pruner = opts.prune ? new prune.Pruner(opts.dir, opts.quiet) : null + + return async function filter (file) { + const fullPath = path.resolve(file) + + if (ignoredOutDirs.includes(fullPath)) { + return false + } + + if (opts.junk !== false) { // defaults to true + if (junk.is(path.basename(fullPath))) { + return false + } + } + + let name = fullPath.split(path.resolve(opts.dir))[1] + + if (path.sep === '\\') { + name = common.normalizePath(name) + } + + if (pruner && name.startsWith('/node_modules/')) { + if (await prune.isModule(file)) { + return pruner.pruneModule(name) + } else { + return filterFunc(name) + } + } + + return filterFunc(name) + } +} + +module.exports = { + populateIgnoredPaths, + generateIgnoredOutDirs, + userPathFilter +} diff --git a/desktop/node_modules/electron-packager/src/download.js b/desktop/node_modules/electron-packager/src/download.js new file mode 100644 index 0000000..9c104a0 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/download.js @@ -0,0 +1,37 @@ +'use strict' + +const common = require('./common') +const debug = require('debug')('electron-packager') +const { downloadArtifact } = require('@electron/get') +const semver = require('semver') +const targets = require('./targets') + +function createDownloadOpts (opts, platform, arch) { + const downloadOpts = { ...opts.download } + + common.subOptionWarning(downloadOpts, 'download', 'platform', platform, opts.quiet) + common.subOptionWarning(downloadOpts, 'download', 'arch', arch, opts.quiet) + common.subOptionWarning(downloadOpts, 'download', 'version', opts.electronVersion, opts.quiet) + common.subOptionWarning(downloadOpts, 'download', 'artifactName', 'electron', opts.quiet) + + return downloadOpts +} + +module.exports = { + createDownloadCombos: function createDownloadCombos (opts, selectedPlatforms, selectedArchs, ignoreFunc) { + return targets.createPlatformArchPairs(opts, selectedPlatforms, selectedArchs, ignoreFunc).map(([platform, arch]) => { + return createDownloadOpts(opts, platform, arch) + }) + }, + createDownloadOpts: createDownloadOpts, + downloadElectronZip: async function downloadElectronZip (downloadOpts) { + // armv7l builds have only been backfilled for Electron >= 1.0.0. + // See: https://github.com/electron/electron/pull/6986 + /* istanbul ignore if */ + if (downloadOpts.arch === 'armv7l' && semver.lt(downloadOpts.version, '1.0.0')) { + downloadOpts.arch = 'arm' + } + debug(`Downloading Electron with options ${JSON.stringify(downloadOpts)}`) + return downloadArtifact(downloadOpts) + } +} diff --git a/desktop/node_modules/electron-packager/src/hooks.js b/desktop/node_modules/electron-packager/src/hooks.js new file mode 100644 index 0000000..a16426a --- /dev/null +++ b/desktop/node_modules/electron-packager/src/hooks.js @@ -0,0 +1,24 @@ +'use strict' + +const { promisify } = require('util') + +module.exports = { + promisifyHooks: async function promisifyHooks (hooks, args) { + if (!hooks || !Array.isArray(hooks)) { + return Promise.resolve() + } + + await Promise.all(hooks.map(hookFn => promisify(hookFn).apply(this, args))) + }, + serialHooks: function serialHooks (hooks) { + return async function () { + const args = Array.prototype.splice.call(arguments, 0, arguments.length - 1) + const done = arguments[arguments.length - 1] + for (const hook of hooks) { + await hook.apply(this, args) + } + + return done() // eslint-disable-line promise/no-callback-in-promise + } + } +} diff --git a/desktop/node_modules/electron-packager/src/index.d.ts b/desktop/node_modules/electron-packager/src/index.d.ts new file mode 100644 index 0000000..5a2b63e --- /dev/null +++ b/desktop/node_modules/electron-packager/src/index.d.ts @@ -0,0 +1,607 @@ +// Originally based on the type definitions for electron-packager 14.0 +// Project: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/electron-packager +// Original Authors: +// * Maxime LUCE <https://github.com/SomaticIT> +// * Juan Jimenez-Anca <https://github.com/cortopy> +// * John Kleinschmidt <https://github.com/jkleinsc> +// * Brendan Forster <https://github.com/shiftkey> +// * Mark Lee <https://github.com/malept> +// * Florian Keller <https://github.com/ffflorian> + +import { CreateOptions as AsarOptions } from '@electron/asar'; +import { ElectronDownloadRequestOptions as ElectronDownloadOptions } from '@electron/get'; +import { + LegacyNotarizeCredentials, + NotaryToolCredentials, + TransporterOptions +} from '@electron/notarize/lib/types'; +import { SignOptions } from '@electron/osx-sign/dist/esm/types'; +import type { makeUniversalApp } from '@electron/universal'; + +type MakeUniversalOpts = Parameters<typeof makeUniversalApp>[0] + +type NotarizeLegacyOptions = LegacyNotarizeCredentials & TransporterOptions; + +/** + * Bundles Electron-based application source code with a renamed/customized Electron executable and + * its supporting files into folders ready for distribution. + * + * Briefly, this function: + * - finds or downloads the correct release of Electron + * - uses that version of Electron to create a app in `<out>/<appname>-<platform>-<arch>` + * + * Short example: + * + * ```javascript + * const packager = require('electron-packager') + * + * async function bundleElectronApp(options) { + * const appPaths = await packager(options) + * console.log(`Electron app bundles created:\n${appPaths.join("\n")}`) + * } + * ``` + * + * @param opts - Options to configure packaging. + * + * @returns A Promise containing the paths to the newly created application bundles. + */ +declare function electronPackager(opts: electronPackager.Options): Promise<string[]>; + +declare namespace electronPackager { + /** + * Architectures that have been supported by the official Electron prebuilt binaries, past + * and present. + */ + type OfficialArch = 'ia32' | 'x64' | 'armv7l' | 'arm64' | 'mips64el' | 'universal'; + /** + * Platforms that have been supported by the official Electron prebuilt binaries, past and present. + */ + type OfficialPlatform = 'linux' | 'win32' | 'darwin' | 'mas'; + type TargetArch = OfficialArch | string; + type TargetPlatform = OfficialPlatform | string; + type ArchOption = TargetArch | 'all'; + type PlatformOption = TargetPlatform | 'all'; + + /** + * A predicate function that, given an absolute file `path`, returns `true` if the file should be + * ignored, or `false` if the file should be kept. *This does not use any of the default ignored + * files/directories listed for the {@link ignore} option.* + */ + type IgnoreFunction = (path: string) => boolean; + /** + * A function that is called on the completion of a packaging stage. + * + * By default, the functions are called in parallel (via + * [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all)). + * If you need the functions called serially, there is a utility function provided. Please note that + * **callback-style functions are not supported by `serialHooks`.** For example: + * + * ```javascript + * const packager = require('electron-packager') + * const { serialHooks } = require('electron-packager/src/hooks') + * + * packager({ + * // ... + * afterCopy: [serialHooks([ + * (buildPath, electronVersion, platform, arch) => { + * return new Promise((resolve, reject) => { + * setTimeout(() => { + * console.log('first function') + * resolve() + * }, 1000) + * }) + * }, + * (buildPath, electronVersion, platform, arch) => { + * console.log('second function') + * } + * ])], + * // ... + * }) + * ``` + * + * For real-world examples of `HookFunction`s, see the [list of related + * plugins](https://github.com/electron/electron-packager#plugins). + */ + type HookFunction = + /** + * @param buildPath - For {@link afterExtract}, the path to the temporary folder where the prebuilt + * Electron binary has been extracted to. For {@link afterCopy} and {@link afterPrune}, the path to the + * folder where the Electron app has been copied to. For {@link afterComplete}, the final directory + * of the packaged application. + * @param electronVersion - the version of Electron that is being bundled with the application. + * @param platform - The target platform you are packaging for. + * @param arch - The target architecture you are packaging for. + * @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; + + type TargetDefinition = { + arch: TargetArch; + platform: TargetPlatform; + } + 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<SignOptions, 'app' | 'binaries' | 'platform' | 'version'>; + + /** + * See the documentation for [`@electron/notarize`](https://npm.im/@electron/notarize#method-notarizeopts-promisevoid) + * for details. + */ + type OsxNotarizeOptions = + | ({ tool?: 'legacy' } & NotarizeLegacyOptions) + | ({ tool: 'notarytool' } & NotaryToolCredentials); + + /** + * See the documentation for [`@electron/universal`](https://github.com/electron/universal) + * for details. + */ + type OsxUniversalOptions = Omit<MakeUniversalOpts, 'x64AppPath' | 'arm64AppPath' | 'outAppPath' | 'force'> + + /** + * Defines URL protocol schemes to be used on macOS. + */ + interface MacOSProtocol { + /** + * The descriptive name. Maps to the `CFBundleURLName` metadata property. + */ + name: string; + /** + * One or more protocol schemes associated with the app. For example, specifying `myapp` + * would cause URLs such as `myapp://path` to be opened with the app. Maps to the + * `CFBundleURLSchemes` metadata property. + */ + schemes: string[]; + } + + /** + * A collection of application metadata to embed into the Windows executable. + * + * For more information, read the [`rcedit` Node module documentation](https://github.com/electron/node-rcedit#docs). + */ + interface Win32MetadataOptions { + /** Defaults to the `author` name from the nearest `package.json`. */ + CompanyName?: string; + /** Defaults to either `productName` or `name` from the nearest `package.json`. */ + FileDescription?: string; + /** Defaults to the renamed Electron `.exe` file. */ + OriginalFilename?: string; + /** Defaults to either `productName` or `name` from the nearest `package.json`. */ + ProductName?: string; + /** Defaults to either `productName` or `name` from the nearest `package.json`. */ + InternalName?: string; + /** See [MSDN](https://msdn.microsoft.com/en-us/library/6ad1fshk.aspx#Anchor_9) for details. */ + 'requested-execution-level'?: 'asInvoker' | 'highestAvailable' | 'requireAdministrator'; + /** + * Path to a local manifest file. + * + * See [MSDN](https://msdn.microsoft.com/en-us/library/windows/desktop/aa374191.aspx) for more details. + */ + 'application-manifest'?: string; + } + + /** Options passed to the `packager()` function. */ + interface Options { + /** The source directory. */ + dir: string; + /** + * Functions to be called after your app directory has been packaged into an .asar file. + * + * **Note**: `afterAsar` will only be called if the {@link asar} option is set. + */ + afterAsar?: HookFunction[]; + /** Functions to be called after the packaged application has been moved to the final directory. */ + afterComplete?: HookFunction[]; + /** + * Functions to be called after your app directory has been copied to a temporary directory. + * + * **Note**: `afterCopy` will not be called if the {@link prebuiltAsar} option is set. + */ + afterCopy?: HookFunction[]; + /** + * Functions to be called after the files specified in the {@link extraResource} option have been copied. + **/ + afterCopyExtraResources?: HookFunction[]; + /** Functions to be called after the prebuilt Electron binary has been extracted to a temporary directory. */ + afterExtract?: HookFunction[]; + /** + * Functions to be called after the final matrix of platform/arch combination is determined. Use this to + * learn what archs/platforms packager is targetting when you pass "all" as a value. + */ + afterFinalizePackageTargets?: FinalizePackageTargetsHookFunction[]; + /** + * Functions to be called after Node module pruning has been applied to the application. + * + * **Note**: None of these functions will be called if the {@link prune} option is `false` or + * the {@link prebuiltAsar} option is set. + */ + afterPrune?: HookFunction[]; + + /** When `true`, sets both {@link arch} and {@link platform} to `all`. */ + all?: boolean; + /* + * The bundle identifier to use in the application's `Info.plist`. + * + * @category macOS + */ + appBundleId?: string; + /** + * The application category type, as shown in the Finder via *View → Arrange by Application + * Category* when viewing the Applications directory. + * + * For example, `app-category-type=public.app-category.developer-tools` will set the + * application category to *Developer Tools*. + * + * Valid values are listed in [Apple's documentation](https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/LaunchServicesKeys.html#//apple_ref/doc/uid/TP40009250-SW8). + * + * @category macOS + */ + appCategoryType?: string; + /** + * The human-readable copyright line for the app. Maps to the `LegalCopyright` metadata + * property on Windows, and `NSHumanReadableCopyright` on macOS. + */ + appCopyright?: string; + /** + * The release version of the application. + * + * By default the `version` property in the `package.json` is used, but it can be overridden + * with this argument. If neither are provided, the version of Electron will be used. Maps + * to the `ProductVersion` metadata property on Windows, and `CFBundleShortVersionString` + * on macOS. + */ + appVersion?: string; + /** + * The target system architecture(s) to build for. + * + * Not required if the {@link all} option is set. If `arch` is set to `all`, all supported + * architectures for the target platforms specified by {@link platform} will be built. + * Arbitrary combinations of individual architectures are also supported via a comma-delimited + * string or array of strings. The non-`all` values correspond to the architecture names used + * by [Electron releases](https://github.com/electron/electron/releases). This value + * is not restricted to the official set if [[download|`download.mirrorOptions`]] is set. + * + * Defaults to the arch of the host computer running Electron Packager. + * + * Arch values for the official prebuilt Electron binaries: + * - `ia32` + * - `x64` + * - `armv7l` + * - `arm64` _(Linux: Electron 1.8.0 and above; Windows: 6.0.8 and above; macOS: 11.0.0-beta.1 and above)_ + * - `mips64el` _(Electron 1.8.2-beta.5 to 1.8.8)_ + */ + arch?: ArchOption | ArchOption[]; + /** + * Whether to package the application's source code into an archive, using [Electron's + * archive format](https://github.com/electron/asar). Reasons why you may want to enable + * this feature include mitigating issues around long path names on Windows, slightly speeding + * up `require`, and concealing your source code from cursory inspection. When the value + * is `true`, it passes the default configuration to the `asar` module. The configuration + * values can be customized when the value is an `Object`. Supported sub-options include, but + * are not limited to: + * - `ordering` (*string*): A path to an ordering file for packing files. An explanation can be + * found on the [Atom issue tracker](https://github.com/atom/atom/issues/10163). + * - `unpack` (*string*): A [glob expression](https://github.com/isaacs/minimatch#features), + * when specified, unpacks the file with matching names to the `app.asar.unpacked` directory. + * - `unpackDir` (*string*): Unpacks the dir to the `app.asar.unpacked` directory whose names + * exactly or pattern match this string. The `asar.unpackDir` is relative to {@link dir}. + * + * Defaults to `false`. + * + * Some examples: + * + * - `asar.unpackDir = 'sub_dir'` will unpack the directory `/<dir>/sub_dir` + * - `asar.unpackDir = path.join('**', '{sub_dir1/sub_sub_dir,sub_dir2}', '*')` will unpack the directories `/<dir>/sub_dir1/sub_sub_dir` and `/<dir>/sub_dir2`, but it will not include their subdirectories. + * - `asar.unpackDir = path.join('**', '{sub_dir1/sub_sub_dir,sub_dir2}', '**')` will unpack the subdirectories of the directories `/<dir>/sub_dir1/sub_sub_dir` and `/<dir>/sub_dir2`. + * - `asar.unpackDir = path.join('**', '{sub_dir1/sub_sub_dir,sub_dir2}', '**', '*')` will unpack the directories `/<dir>/sub_dir1/sub_sub_dir` and `/<dir>/sub_dir2` and their subdirectories. + * + * **Note:** `asar` will have no effect if the {@link prebuiltAsar} option is set. + */ + asar?: boolean | AsarOptions; + /** + * Functions to be called before your app directory is packaged into an .asar file. + * + * **Note**: `beforeAsar` will only be called if the {@link asar} option is set. + */ + beforeAsar?: HookFunction[]; + /** + * Functions to be called before your app directory is copied to a temporary directory. + * + * **Note**: `beforeCopy` will not be called if the {@link prebuiltAsar} option is set. + */ + beforeCopy?: HookFunction[]; + /** + * Functions to be called before the files specified in the {@link extraResource} option are copied. + **/ + beforeCopyExtraResources?: HookFunction[]; + /** + * The build version of the application. Defaults to the value of the {@link appVersion} option. + * Maps to the `FileVersion` metadata property on Windows, and `CFBundleVersion` on macOS. + */ + buildVersion?: string; + /** + * Forces support for Mojave (macOS 10.14) dark mode in your packaged app. This sets the + * `NSRequiresAquaSystemAppearance` key to `false` in your app's `Info.plist`. For more information, + * see the [Electron documentation](https://www.electronjs.org/docs/tutorial/mojave-dark-mode-guide) + * and the [Apple developer documentation](https://developer.apple.com/documentation/appkit/nsappearancecustomization/choosing_a_specific_appearance_for_your_app). + * + * @category macOS + */ + darwinDarkModeSupport?: boolean; + /** + * Whether symlinks should be dereferenced during the copying of the application source. + * Defaults to `true`. + * + * **Note:** `derefSymlinks` will have no effect if the {@link prebuiltAsar} option is set. + */ + derefSymlinks?: boolean; + /** + * If present, passes custom options to [`@electron/get`](https://npm.im/@electron/get). See + * the module for option descriptions, proxy support, and defaults. Supported parameters + * include, but are not limited to: + * - `cacheRoot` (*string*): The directory where prebuilt, pre-packaged Electron downloads are cached. + * - `mirrorOptions` (*Object*): Options to override the default Electron download location. + * - `rejectUnauthorized` (*boolean* - default: `true`): Whether SSL certificates are required to be + * valid when downloading Electron. + * + * **Note:** `download` sub-options will have no effect if the {@link electronZipDir} option is set. + */ + download?: ElectronDownloadOptions; + /** + * The Electron version with which the app is built (without the leading 'v') - for example, + * [`1.4.13`](https://github.com/electron/electron/releases/tag/v1.4.13). See [Electron + * releases](https://github.com/electron/electron/releases) for valid versions. If omitted, it + * will use the version of the nearest local installation of `electron`, + * `electron-prebuilt-compile`, or `electron-prebuilt`, defined in `package.json` in either + * `devDependencies` or `dependencies`. + */ + electronVersion?: string; + /** + * The local path to a directory containing Electron ZIP files for Electron Packager to unzip, instead + * of downloading them. The ZIP filenames should be in the same format as the ones downloaded from the + * [Electron releases](https://github.com/electron/electron/releases) site. + * + * **Note:** Setting this option prevents the {@link download} sub-options from being used, as + * the functionality gets skipped over. + */ + electronZipDir?: string; + /** + * The name of the executable file, sans file extension. Defaults to the value for the {@link name} + * option. For `darwin` or `mas` target platforms, this does not affect the name of the + * `.app` folder - this will use the {@link name} option instead. + */ + executableName?: string; + /** + * When the value is a string, specifies the filename of a `plist` file. Its contents are merged + * into the app's `Info.plist`. + * When the value is an `Object`, it specifies an already-parsed `plist` data structure that is + * merged into the app's `Info.plist`. + * + * Entries from `extendInfo` override entries in the base `Info.plist` file supplied by + * `electron`, `electron-prebuilt-compile`, or `electron-prebuilt`, but are overridden by other + * options such as {@link appVersion} or {@link appBundleId}. + * + * @category macOS + */ + extendInfo?: string | { [property: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any + /** + * When the value is a string, specifies the filename of a `plist` file. Its contents are merged + * into all the Helper apps' `Info.plist` files. + * When the value is an `Object`, it specifies an already-parsed `plist` data structure that is + * merged into all the Helper apps' `Info.plist` files. + * + * Entries from `extendHelperInfo` override entries in the helper apps' `Info.plist` file supplied by + * `electron`, `electron-prebuilt-compile`, or `electron-prebuilt`, but are overridden by other + * options such as {@link appVersion} or {@link appBundleId}. + * + * @category macOS + */ + extendHelperInfo?: string | { [property: string]: any }; // eslint-disable-line @typescript-eslint/no-explicit-any + /** + * One or more files to be copied directly into the app's `Contents/Resources` directory for + * macOS target platforms, and the `resources` directory for other target platforms. The + * resources directory can be referenced in the packaged app via the + * [`process.resourcesPath`](https://www.electronjs.org/docs/api/process#processresourcespath-readonly) value. + */ + extraResource?: string | string[]; + /** + * The bundle identifier to use in the application helper's `Info.plist`. + * + * @category macOS + */ + helperBundleId?: string; + /** + * The local path to the icon file, if the target platform supports setting embedding an icon. + * + * Currently you must look for conversion tools in order to supply an icon in the format required by the platform: + * + * - macOS: `.icns` + * - Windows: `.ico` ([See the readme](https://github.com/electron/electron-packager#building-windows-apps-from-non-windows-platforms) for details on non-Windows platforms) + * - Linux: this option is not supported, as the dock/window list icon is set via + * [the `icon` option in the `BrowserWindow` constructor](https://electronjs.org/docs/api/browser-window/#new-browserwindowoptions). + * *Please note that you need to use a PNG, and not the macOS or Windows icon formats, in order for it + * to show up in the dock/window list.* Setting the icon in the file manager is not currently supported. + * + * If the file extension is omitted, it is auto-completed to the correct extension based on the + * platform, including when [[platform|`platform: 'all'`]] is in effect. + */ + icon?: string; + /** + * One or more additional [regular expression](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) + * patterns which specify which files to ignore when copying files to create the app bundle(s). The + * regular expressions are matched against the absolute path of a given file/directory to be copied. + * + * **Please note that [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29) will not work.** + * + * The following paths are always ignored (*when you aren't using an {@link IgnoreFunction}*): + * + * - the directory specified by the {@link out} option + * - the temporary directory used to build the Electron app + * - `node_modules/.bin` + * - `node_modules/electron` + * - `node_modules/electron-prebuilt` + * - `node_modules/electron-prebuilt-compile` + * - `.git` + * - files and folders ending in `.o` and `.obj` + * + * **Note**: Node modules specified in `devDependencies` are ignored by default, via the + * {@link prune} option. + * + * **Note:** `ignore` will have no effect if the {@link prebuiltAsar} option is set. + */ + ignore?: RegExp | RegExp[] | IgnoreFunction; + /** + * Ignores [system junk files](https://github.com/sindresorhus/junk) when copying the Electron app, + * regardless of the {@link ignore} option. + * + * **Note:** `junk` will have no effect if the {@link prebuiltAsar} option is set. + */ + junk?: boolean; + /** + * The application name. If omitted, it will use the `productName` or `name` value from the + * nearest `package.json`. + * + * **Regardless of source, characters in the Electron app name which are not allowed in all target + * platforms' filenames (e.g., `/`), will be replaced by hyphens (`-`).** + */ + name?: string; + /** + * If present, notarizes macOS target apps when the host platform is macOS and Xcode is installed. + * See [`@electron/notarize`](https://github.com/electron/notarize#method-notarizeopts-promisevoid) + * for option descriptions, such as how to use `appleIdPassword` safely or obtain an API key. + * + * **Requires the {@link osxSign} option to be set.** + * + * @category macOS + */ + osxNotarize?: OsxNotarizeOptions; + /** + * If present, signs macOS target apps when the host platform is macOS and Xcode is installed. + * When the value is `true`, pass default configuration to the signing module. See + * [@electron/osx-sign](https://npm.im/@electron/osx-sign#opts---options) for sub-option descriptions and + * their defaults. Options include, but are not limited to: + * - `identity` (*string*): The identity used when signing the package via `codesign`. + * - `binaries` (*array<string>*): Path to additional binaries that will be signed along with built-ins of Electron/ + * + * @category macOS + */ + osxSign?: true | OsxSignOptions; + /** + * Used to provide custom options to the internal call to `@electron/universal` when building a macOS + * app with the target architecture of "universal". Unused otherwise, providing a value does not imply + * a universal app is built. + */ + osxUniversal?: OsxUniversalOptions; + /** + * The base directory where the finished package(s) are created. + * + * Defaults to the current working directory. + */ + out?: string; + /** + * Whether to replace an already existing output directory for a given platform (`true`) or + * skip recreating it (`false`). Defaults to `false`. + */ + overwrite?: boolean; + /** + * The target platform(s) to build for. + * + * Not required if the {@link all} option is set. If `platform` is set to `all`, all officially + * supported target platforms for the target architectures specified by the {@link arch} option + * will be built. Arbitrary combinations of individual platforms are also supported via a + * comma-delimited string or array of strings. + * + * The official non-`all` values correspond to the platform names used by [Electron + * releases](https://github.com/electron/electron/releases). This value is not restricted to + * the official set if [[download|`download.mirrorOptions]] is set. + * + * Defaults to the platform of the host computer running Electron Packager. + * + * Platform values for the official prebuilt Electron binaries: + * - `darwin` (macOS) + * - `linux` + * - `mas` (macOS, specifically for submitting to the Mac App Store) + * - `win32` + */ + platform?: PlatformOption | PlatformOption[]; + /** + * The path to a prebuilt ASAR file. + * + * **Note:** Setting this option prevents the following options from being used, as the functionality + * gets skipped over: + * + * - {@link asar} + * - {@link afterCopy} + * - {@link afterPrune} + * - {@link derefSymlinks} + * - {@link ignore} + * - {@link junk} + * - {@link prune} + */ + prebuiltAsar?: string; + /** + * The URL protocol schemes associated with the Electron app. + * + * @category macOS + */ + protocols?: MacOSProtocol[]; + /** + * Walks the `node_modules` dependency tree to remove all of the packages specified in the + * `devDependencies` section of `package.json` from the outputted Electron app. + * + * Defaults to `true`. + * + * **Note:** `prune` will have no effect if the {@link prebuiltAsar} option is set. + */ + prune?: boolean; + /** + * If `true`, disables printing informational and warning messages to the console when + * packaging the application. This does not disable errors. + * + * Defaults to `false`. + */ + quiet?: boolean; + /** + * The base directory to use as a temporary directory. Set to `false` to disable use of a + * temporary directory. Defaults to the system's temporary directory. + */ + tmpdir?: string | false; + /** + * Human-readable descriptions of how the Electron app uses certain macOS features. These are displayed + * in the App Store. A non-exhaustive list of available properties: + * + * * `Camera` - required for media access API usage in macOS Catalina + * * `Microphone` - required for media access API usage in macOS Catalina + * + * Valid properties are the [Cocoa keys for MacOS](https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html) + * of the pattern `NS(.*)UsageDescription`, where the captured group is the key to use. + * + * Example: + * + * ```javascript + * { + * usageDescription: { + * Camera: 'Needed for video calls', + * Microphone: 'Needed for voice calls' + * } + * } + * ``` + * + * @category macOS + */ + usageDescription?: { [property: string]: string }; + /** + * Application metadata to embed into the Windows executable. + * @category Windows + */ + win32metadata?: Win32MetadataOptions; + } +} + +export = electronPackager; diff --git a/desktop/node_modules/electron-packager/src/index.js b/desktop/node_modules/electron-packager/src/index.js new file mode 100644 index 0000000..d64afd4 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/index.js @@ -0,0 +1,207 @@ +'use strict' + +const common = require('./common') +const copyFilter = require('./copy-filter') +const debug = require('debug')('electron-packager') +const download = require('./download') +const fs = require('fs-extra') +const getMetadataFromPackageJSON = require('./infer') +const hooks = require('./hooks') +const path = require('path') +const targets = require('./targets') +const unzip = require('./unzip') +const { packageUniversalMac } = require('./universal') + +function debugHostInfo () { + debug(common.hostInfo()) +} + +class Packager { + constructor (opts) { + this.opts = opts + this.tempBase = common.baseTempDir(opts) + this.useTempDir = opts.tmpdir !== false + this.canCreateSymlinks = undefined + } + + async ensureTempDir () { + if (this.useTempDir) { + await fs.remove(this.tempBase) + } else { + return Promise.resolve() + } + } + + async testSymlink (comboOpts, zipPath) { + await fs.mkdirp(this.tempBase) + const testPath = await fs.mkdtemp(path.join(this.tempBase, `symlink-test-${comboOpts.platform}-${comboOpts.arch}-`)) + const testFile = path.join(testPath, 'test') + const testLink = path.join(testPath, 'testlink') + + try { + await fs.outputFile(testFile, '') + await fs.symlink(testFile, testLink) + this.canCreateSymlinks = true + } catch (e) { + /* istanbul ignore next */ + this.canCreateSymlinks = false + } finally { + await fs.remove(testPath) + } + + if (this.canCreateSymlinks) { + return this.checkOverwrite(comboOpts, zipPath) + } + + /* istanbul ignore next */ + return this.skipHostPlatformSansSymlinkSupport(comboOpts) + } + + /* istanbul ignore next */ + skipHostPlatformSansSymlinkSupport (comboOpts) { + common.info(`Cannot create symlinks (on Windows hosts, it requires admin privileges); skipping ${comboOpts.platform} platform`, this.opts.quiet) + return Promise.resolve() + } + + async overwriteAndCreateApp (outDir, comboOpts, zipPath) { + debug(`Removing ${outDir} due to setting overwrite: true`) + await fs.remove(outDir) + return this.createApp(comboOpts, zipPath) + } + + async extractElectronZip (comboOpts, zipPath, buildDir) { + debug(`Extracting ${zipPath} to ${buildDir}`) + await unzip(zipPath, buildDir) + await hooks.promisifyHooks(this.opts.afterExtract, [buildDir, comboOpts.electronVersion, comboOpts.platform, comboOpts.arch]) + } + + async buildDir (platform, arch) { + let buildParentDir + if (this.useTempDir) { + buildParentDir = this.tempBase + } else { + buildParentDir = this.opts.out || process.cwd() + } + await fs.mkdirp(buildParentDir) + return await fs.mkdtemp(path.resolve(buildParentDir, `${platform}-${arch}-template-`)) + } + + async createApp (comboOpts, zipPath) { + const buildDir = await this.buildDir(comboOpts.platform, comboOpts.arch) + common.info(`Packaging app for platform ${comboOpts.platform} ${comboOpts.arch} using electron v${comboOpts.electronVersion}`, this.opts.quiet) + + debug(`Creating ${buildDir}`) + await fs.ensureDir(buildDir) + await this.extractElectronZip(comboOpts, zipPath, buildDir) + const os = require(targets.osModules[comboOpts.platform]) + const app = new os.App(comboOpts, buildDir) + return app.create() + } + + async checkOverwrite (comboOpts, zipPath) { + const finalPath = common.generateFinalPath(comboOpts) + if (await fs.pathExists(finalPath)) { + if (this.opts.overwrite) { + return this.overwriteAndCreateApp(finalPath, comboOpts, zipPath) + } else { + common.info(`Skipping ${comboOpts.platform} ${comboOpts.arch} (output dir already exists, use --overwrite to force)`, this.opts.quiet) + return true + } + } else { + return this.createApp(comboOpts, zipPath) + } + } + + async getElectronZipPath (downloadOpts) { + if (this.opts.electronZipDir) { + if (await fs.pathExists(this.opts.electronZipDir)) { + const zipPath = path.resolve( + this.opts.electronZipDir, + `electron-v${downloadOpts.version}-${downloadOpts.platform}-${downloadOpts.arch}.zip` + ) + if (!await fs.pathExists(zipPath)) { + throw new Error(`The specified Electron ZIP file does not exist: ${zipPath}`) + } + + return zipPath + } + + throw new Error(`The specified Electron ZIP directory does not exist: ${this.opts.electronZipDir}`) + } else { + return download.downloadElectronZip(downloadOpts) + } + } + + async packageForPlatformAndArchWithOpts (comboOpts, downloadOpts) { + const zipPath = await this.getElectronZipPath(downloadOpts) + + if (!this.useTempDir) { + return this.createApp(comboOpts, zipPath) + } + + if (common.isPlatformMac(comboOpts.platform)) { + /* istanbul ignore else */ + if (this.canCreateSymlinks === undefined) { + return this.testSymlink(comboOpts, zipPath) + } else if (!this.canCreateSymlinks) { + return this.skipHostPlatformSansSymlinkSupport(comboOpts) + } + } + + return this.checkOverwrite(comboOpts, zipPath) + } + + async packageForPlatformAndArch (downloadOpts) { + // Create delegated options object with specific platform and arch, for output directory naming + const comboOpts = { + ...this.opts, + arch: downloadOpts.arch, + platform: downloadOpts.platform, + electronVersion: downloadOpts.version + } + + if (common.isPlatformMac(comboOpts.platform) && comboOpts.arch === 'universal') { + return packageUniversalMac(this.packageForPlatformAndArchWithOpts.bind(this), await this.buildDir(comboOpts.platform, comboOpts.arch), comboOpts, downloadOpts, this.tempBase) + } + + return this.packageForPlatformAndArchWithOpts(comboOpts, downloadOpts) + } +} + +async function packageAllSpecifiedCombos (opts, archs, platforms) { + const packager = new Packager(opts) + await packager.ensureTempDir() + return Promise.all(download.createDownloadCombos(opts, platforms, archs).map( + downloadOpts => packager.packageForPlatformAndArch(downloadOpts) + )) +} + +module.exports = async function packager (opts) { + debugHostInfo() + if (debug.enabled) debug(`Packager Options: ${JSON.stringify(opts)}`) + + const archs = targets.validateListFromOptions(opts, 'arch') + const platforms = targets.validateListFromOptions(opts, 'platform') + if (!Array.isArray(archs)) return Promise.reject(archs) + if (!Array.isArray(platforms)) return Promise.reject(platforms) + + debug(`Target Platforms: ${platforms.join(', ')}`) + debug(`Target Architectures: ${archs.join(', ')}`) + + const packageJSONDir = path.resolve(process.cwd(), opts.dir) || process.cwd() + + await getMetadataFromPackageJSON(platforms, opts, packageJSONDir) + if (opts.name.endsWith(' Helper')) { + throw new Error('Application names cannot end in " Helper" due to limitations on macOS') + } + + debug(`Application name: ${opts.name}`) + debug(`Target Electron version: ${opts.electronVersion}`) + + copyFilter.populateIgnoredPaths(opts) + + await hooks.promisifyHooks(opts.afterFinalizePackageTargets, [targets.createPlatformArchPairs(opts, platforms, archs).map(([platform, arch]) => ({ platform, arch }))]) + const appPaths = await packageAllSpecifiedCombos(opts, archs, platforms) + // Remove falsy entries (e.g. skipped platforms) + return appPaths.filter(appPath => appPath) +} diff --git a/desktop/node_modules/electron-packager/src/infer.js b/desktop/node_modules/electron-packager/src/infer.js new file mode 100644 index 0000000..9dde6d7 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/infer.js @@ -0,0 +1,178 @@ +'use strict' + +const debug = require('debug')('electron-packager') +const getPackageInfo = require('get-package-info') +const parseAuthor = require('parse-author') +const path = require('path') +const resolve = require('resolve') +const semver = require('semver') + +function isMissingRequiredProperty (props) { + return props.some(prop => prop === 'productName' || prop === 'dependencies.electron') +} + +function errorMessageForProperty (prop) { + let hash, propDescription + switch (prop) { + case 'productName': + hash = 'name' + propDescription = 'application name' + break + case 'dependencies.electron': + hash = 'electronversion' + propDescription = 'Electron version' + break + case 'version': + hash = 'appversion' + propDescription = 'application version' + break + /* istanbul ignore next */ + default: + hash = '' + propDescription = `[Unknown Property (${prop})]` + } + + return `Unable to determine ${propDescription}. Please specify an ${propDescription}\n\n` + + 'For more information, please see\n' + + `https://electron.github.io/electron-packager/main/interfaces/electronpackager.options.html#${hash}\n` +} + +function resolvePromise (id, options) { + // eslint-disable-next-line promise/param-names + return new Promise((accept, reject) => { + resolve(id, options, (err, mainPath, pkg) => { + if (err) { + /* istanbul ignore next */ + reject(err) + } else { + accept([mainPath, pkg]) + } + }) + }) +} + +function rangeFromElectronVersion (electronVersion) { + try { + return new semver.Range(electronVersion) + } catch (error) { + return null + } +} + +async function getVersion (opts, electronProp) { + const [depType, packageName] = electronProp.prop.split('.') + const src = electronProp.src + if (packageName === 'electron-prebuilt-compile') { + const electronVersion = electronProp.pkg[depType][packageName] + const versionRange = rangeFromElectronVersion(electronVersion) + if (versionRange !== null && versionRange.intersects(new semver.Range('< 1.6.5'))) { + if (!/^\d+\.\d+\.\d+/.test(electronVersion)) { + // electron-prebuilt-compile cannot be resolved because `main` does not point + // to a valid JS file. + throw new Error('Using electron-prebuilt-compile with Electron Packager requires specifying an exact Electron version') + } + + opts.electronVersion = electronVersion + return Promise.resolve() + } + } + + const pkg = (await resolvePromise(packageName, { basedir: path.dirname(src) }))[1] + debug(`Inferring target Electron version from ${packageName} in ${src}`) + opts.electronVersion = pkg.version + return null +} + +async function handleMetadata (opts, result) { + if (result.values.productName) { + debug(`Inferring application name from ${result.source.productName.prop} in ${result.source.productName.src}`) + opts.name = result.values.productName + } + + if (result.values.version) { + debug(`Inferring appVersion from version in ${result.source.version.src}`) + opts.appVersion = result.values.version + } + + if (result.values.author && !opts.win32metadata) { + opts.win32metadata = {} + } + + if (result.values.author) { + debug(`Inferring win32metadata.CompanyName from author in ${result.source.author.src}`) + if (typeof result.values.author === 'string') { + opts.win32metadata.CompanyName = parseAuthor(result.values.author).name + } else if (result.values.author.name) { + opts.win32metadata.CompanyName = result.values.author.name + } else { + debug('Cannot infer win32metadata.CompanyName from author, no name found') + } + } + + // eslint-disable-next-line no-prototype-builtins + if (result.values.hasOwnProperty('dependencies.electron')) { + return getVersion(opts, result.source['dependencies.electron']) + } else { + return Promise.resolve() + } +} + +function handleMissingProperties (opts, err) { + const missingProps = err.missingProps.map(prop => { + return Array.isArray(prop) ? prop[0] : prop + }) + + if (isMissingRequiredProperty(missingProps)) { + const messages = missingProps.map(errorMessageForProperty) + + debug(err.message) + err.message = messages.join('\n') + '\n' + throw err + } else { + // Missing props not required, can continue w/ partial result + return handleMetadata(opts, err.result) + } +} + +module.exports = async function getMetadataFromPackageJSON (platforms, opts, dir) { + const props = [] + if (!opts.name) props.push(['productName', 'name']) + if (!opts.appVersion) props.push('version') + if (!opts.electronVersion) { + props.push([ + 'dependencies.electron', + 'devDependencies.electron', + 'dependencies.electron-nightly', + 'devDependencies.electron-nightly', + 'dependencies.electron-prebuilt-compile', + 'devDependencies.electron-prebuilt-compile', + 'dependencies.electron-prebuilt', + 'devDependencies.electron-prebuilt' + ]) + } + + if (platforms.includes('win32') && !(opts.win32metadata && opts.win32metadata.CompanyName)) { + debug('Requiring author in package.json, as CompanyName was not specified for win32metadata') + props.push('author') + } + + // Name and version provided, no need to infer + if (props.length === 0) return Promise.resolve() + + // Search package.json files to infer name and version from + try { + const result = await getPackageInfo(props, dir) + return handleMetadata(opts, result) + } catch (err) { + if (err.missingProps) { + if (err.missingProps.length === props.length) { + debug(err.message) + err.message = `Could not locate a package.json file in "${path.resolve(opts.dir)}" or its parent directories for an Electron app with the following fields: ${err.missingProps.join(', ')}` + } else { + return handleMissingProperties(opts, err) + } + } + + throw err + } +} diff --git a/desktop/node_modules/electron-packager/src/linux.js b/desktop/node_modules/electron-packager/src/linux.js new file mode 100644 index 0000000..1e98414 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/linux.js @@ -0,0 +1,25 @@ +'use strict' + +const App = require('./platform') +const common = require('./common') + +class LinuxApp extends App { + get originalElectronName () { + return 'electron' + } + + get newElectronName () { + return common.sanitizeAppName(this.executableName) + } + + async create () { + await this.initialize() + await this.renameElectron() + await this.copyExtraResources() + return this.move() + } +} + +module.exports = { + App: LinuxApp +} diff --git a/desktop/node_modules/electron-packager/src/mac.js b/desktop/node_modules/electron-packager/src/mac.js new file mode 100644 index 0000000..6eaf524 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/mac.js @@ -0,0 +1,440 @@ +'use strict' + +const App = require('./platform') +const common = require('./common') +const debug = require('debug')('electron-packager') +const fs = require('fs-extra') +const path = require('path') +const plist = require('plist') +const { notarize } = require('@electron/notarize') +const { signApp } = require('@electron/osx-sign') + +class MacApp extends App { + constructor (opts, templatePath) { + super(opts, templatePath) + + this.appName = opts.name + } + + get appCategoryType () { + return this.opts.appCategoryType + } + + get appCopyright () { + return this.opts.appCopyright + } + + get appVersion () { + return this.opts.appVersion + } + + get buildVersion () { + return this.opts.buildVersion + } + + get enableDarkMode () { + return this.opts.darwinDarkModeSupport + } + + get usageDescription () { + return this.opts.usageDescription + } + + get protocols () { + return this.opts.protocols.map((protocol) => { + return { + CFBundleURLName: protocol.name, + CFBundleURLSchemes: [].concat(protocol.schemes) + } + }) + } + + get dotAppName () { + return `${common.sanitizeAppName(this.appName)}.app` + } + + get defaultBundleName () { + return `com.electron.${common.sanitizeAppName(this.appName).toLowerCase()}` + } + + get bundleName () { + return filterCFBundleIdentifier(this.opts.appBundleId || this.defaultBundleName) + } + + get originalResourcesDir () { + return path.join(this.contentsPath, 'Resources') + } + + get resourcesDir () { + return path.join(this.dotAppName, 'Contents', 'Resources') + } + + get electronBinaryDir () { + return path.join(this.contentsPath, 'MacOS') + } + + get originalElectronName () { + return 'Electron' + } + + get newElectronName () { + return this.appPlist.CFBundleExecutable + } + + get renamedAppPath () { + return path.join(this.stagingPath, this.dotAppName) + } + + get electronAppPath () { + return path.join(this.stagingPath, `${this.originalElectronName}.app`) + } + + get contentsPath () { + return path.join(this.electronAppPath, 'Contents') + } + + get frameworksPath () { + return path.join(this.contentsPath, 'Frameworks') + } + + get loginItemsPath () { + return path.join(this.contentsPath, 'Library', 'LoginItems') + } + + get loginHelperPath () { + return path.join(this.loginItemsPath, 'Electron Login Helper.app') + } + + updatePlist (basePlist, displayName, identifier, name) { + return Object.assign(basePlist, { + CFBundleDisplayName: displayName, + CFBundleExecutable: common.sanitizeAppName(displayName), + CFBundleIdentifier: identifier, + CFBundleName: common.sanitizeAppName(name) + }) + } + + updateHelperPlist (basePlist, suffix, identifierIgnoresSuffix) { + let helperSuffix, identifier, name + if (suffix) { + helperSuffix = `Helper ${suffix}` + if (identifierIgnoresSuffix) { + identifier = this.helperBundleIdentifier + } else { + identifier = `${this.helperBundleIdentifier}.${suffix}` + } + name = `${this.appName} ${helperSuffix}` + } else { + helperSuffix = 'Helper' + identifier = this.helperBundleIdentifier + name = this.appName + } + return this.updatePlist(basePlist, `${this.appName} ${helperSuffix}`, identifier, name) + } + + async extendPlist (basePlist, propsOrFilename) { + if (!propsOrFilename) { + return Promise.resolve() + } + + if (typeof propsOrFilename === 'string') { + const plist = await this.loadPlist(propsOrFilename) + return Object.assign(basePlist, plist) + } else { + return Object.assign(basePlist, propsOrFilename) + } + } + + async loadPlist (filename, propName) { + const loadedPlist = plist.parse((await fs.readFile(filename)).toString()) + if (propName) { + this[propName] = loadedPlist + } + return loadedPlist + } + + ehPlistFilename (helper) { + return this.helperPlistFilename(path.join(this.frameworksPath, helper)) + } + + helperPlistFilename (helperApp) { + return path.join(helperApp, 'Contents', 'Info.plist') + } + + async determinePlistFilesToUpdate () { + const appPlistFilename = path.join(this.contentsPath, 'Info.plist') + + const plists = [ + [appPlistFilename, 'appPlist'], + [this.ehPlistFilename('Electron Helper.app'), 'helperPlist'] + ] + + const possiblePlists = [ + [this.ehPlistFilename('Electron Helper (Renderer).app'), 'helperRendererPlist'], + [this.ehPlistFilename('Electron Helper (Plugin).app'), 'helperPluginPlist'], + [this.ehPlistFilename('Electron Helper (GPU).app'), 'helperGPUPlist'], + [this.ehPlistFilename('Electron Helper EH.app'), 'helperEHPlist'], + [this.ehPlistFilename('Electron Helper NP.app'), 'helperNPPlist'], + [this.helperPlistFilename(this.loginHelperPath), 'loginHelperPlist'] + ] + + const optional = await Promise.all(possiblePlists.map(async item => + (await fs.pathExists(item[0])) ? item : null)) + return plists.concat(optional.filter(item => item)) + } + + appRelativePath (p) { + return path.relative(this.contentsPath, p) + } + + async updatePlistFiles () { + const appBundleIdentifier = this.bundleName + this.helperBundleIdentifier = filterCFBundleIdentifier(this.opts.helperBundleId || `${appBundleIdentifier}.helper`) + + const plists = await this.determinePlistFilesToUpdate() + await Promise.all(plists.map(plistArgs => this.loadPlist(...plistArgs))) + await this.extendPlist(this.appPlist, this.opts.extendInfo) + if (this.asarIntegrity) { + await this.extendPlist(this.appPlist, { + ElectronAsarIntegrity: this.asarIntegrity + }) + } else { + delete this.appPlist.ElectronAsarIntegrity + } + this.appPlist = this.updatePlist(this.appPlist, this.executableName, appBundleIdentifier, this.appName) + + const updateIfExists = [ + ['helperRendererPlist', '(Renderer)', true], + ['helperPluginPlist', '(Plugin)', true], + ['helperGPUPlist', '(GPU)', true], + ['helperEHPlist', 'EH'], + ['helperNPPlist', 'NP'] + ] + + for (const [plistKey] of [...updateIfExists, ['helperPlist']]) { + if (!this[plistKey]) continue + await this.extendPlist(this[plistKey], this.opts.extendHelperInfo) + } + + this.helperPlist = this.updateHelperPlist(this.helperPlist) + for (const [plistKey, ...suffixArgs] of updateIfExists) { + if (!this[plistKey]) continue + this[plistKey] = this.updateHelperPlist(this[plistKey], ...suffixArgs) + } + + // Some properties need to go on all helpers as well, version, usage info, etc. + const plistsToUpdate = updateIfExists + .filter(([key]) => !!this[key]) + .map(([key]) => key) + .concat(['appPlist', 'helperPlist']) + + if (this.loginHelperPlist) { + const loginHelperName = common.sanitizeAppName(`${this.appName} Login Helper`) + this.loginHelperPlist.CFBundleExecutable = loginHelperName + this.loginHelperPlist.CFBundleIdentifier = `${appBundleIdentifier}.loginhelper` + this.loginHelperPlist.CFBundleName = loginHelperName + } + + if (this.appVersion) { + const appVersionString = '' + this.appVersion + for (const plistKey of plistsToUpdate) { + this[plistKey].CFBundleShortVersionString = this[plistKey].CFBundleVersion = appVersionString + } + } + + if (this.buildVersion) { + const buildVersionString = '' + this.buildVersion + for (const plistKey of plistsToUpdate) { + this[plistKey].CFBundleVersion = buildVersionString + } + } + + if (this.opts.protocols && this.opts.protocols.length) { + this.appPlist.CFBundleURLTypes = this.protocols + } + + if (this.appCategoryType) { + this.appPlist.LSApplicationCategoryType = this.appCategoryType + } + + if (this.appCopyright) { + this.appPlist.NSHumanReadableCopyright = this.appCopyright + } + + if (this.enableDarkMode) { + this.appPlist.NSRequiresAquaSystemAppearance = false + } + + if (this.usageDescription) { + for (const [type, description] of Object.entries(this.usageDescription)) { + const usageTypeKey = `NS${type}UsageDescription` + for (const plistKey of plistsToUpdate) { + this[plistKey][usageTypeKey] = description + } + this.appPlist[usageTypeKey] = description + } + } + + await Promise.all(plists.map(([filename, varName]) => + fs.writeFile(filename, plist.build(this[varName])))) + } + + async moveHelpers () { + const helpers = [' Helper', ' Helper EH', ' Helper NP', ' Helper (Renderer)', ' Helper (Plugin)', ' Helper (GPU)'] + await Promise.all(helpers.map(suffix => this.moveHelper(this.frameworksPath, suffix))) + if (await fs.pathExists(this.loginItemsPath)) { + await this.moveHelper(this.loginItemsPath, ' Login Helper') + } + } + + async moveHelper (helperDirectory, suffix) { + const originalBasename = `Electron${suffix}` + + if (await fs.pathExists(path.join(helperDirectory, `${originalBasename}.app`))) { + return this.renameHelperAndExecutable( + helperDirectory, + originalBasename, + `${common.sanitizeAppName(this.appName)}${suffix}` + ) + } else { + return Promise.resolve() + } + } + + async renameHelperAndExecutable (helperDirectory, originalBasename, newBasename) { + const originalAppname = `${originalBasename}.app` + const executableBasePath = path.join(helperDirectory, originalAppname, 'Contents', 'MacOS') + await this.relativeRename(executableBasePath, originalBasename, newBasename) + await this.relativeRename(helperDirectory, originalAppname, `${newBasename}.app`) + } + + async copyIcon () { + if (!this.opts.icon) { + return Promise.resolve() + } + + let icon + + try { + icon = await this.normalizeIconExtension('.icns') + } catch { + // Ignore error if icon doesn't exist, in case it's only available for other OSes + /* istanbul ignore next */ + return Promise.resolve() + } + if (icon) { + debug(`Copying icon "${icon}" to app's Resources as "${this.appPlist.CFBundleIconFile}"`) + await fs.copy(icon, path.join(this.originalResourcesDir, this.appPlist.CFBundleIconFile)) + } + } + + async renameAppAndHelpers () { + await this.moveHelpers() + await fs.rename(this.electronAppPath, this.renamedAppPath) + } + + async signAppIfSpecified () { + const osxSignOpt = this.opts.osxSign + const platform = this.opts.platform + const version = this.opts.electronVersion + + if ((platform === 'all' || platform === 'mas') && + osxSignOpt === undefined) { + common.warning('signing is required for mas builds. Provide the osx-sign option, ' + + 'or manually sign the app later.', this.opts.quiet) + } + + if (osxSignOpt) { + const signOpts = createSignOpts(osxSignOpt, platform, this.renamedAppPath, version, this.opts.quiet) + debug(`Running @electron/osx-sign with the options ${JSON.stringify(signOpts)}`) + try { + await signApp(signOpts) + } catch (err) { + // Although not signed successfully, the application is packed. + common.warning(`Code sign failed; please retry manually. ${err}`, this.opts.quiet) + } + } + } + + async notarizeAppIfSpecified () { + const osxNotarizeOpt = this.opts.osxNotarize + + /* istanbul ignore if */ + if (osxNotarizeOpt) { + const notarizeOpts = createNotarizeOpts( + osxNotarizeOpt, + this.bundleName, + this.renamedAppPath, + this.opts.quiet + ) + if (notarizeOpts) { + return notarize(notarizeOpts) + } + } + } + + async create () { + await this.initialize() + await this.updatePlistFiles() + await this.copyIcon() + await this.renameElectron() + await this.renameAppAndHelpers() + await this.copyExtraResources() + await this.signAppIfSpecified() + await this.notarizeAppIfSpecified() + return this.move() + } +} + +/** + * Remove special characters and allow only alphanumeric (A-Z,a-z,0-9), hyphen (-), and period (.) + * Apple documentation: + * https://developer.apple.com/library/mac/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102070 + */ +function filterCFBundleIdentifier (identifier) { + return identifier.replace(/ /g, '-').replace(/[^a-zA-Z0-9.-]/g, '') +} + +function createSignOpts (properties, platform, app, version, quiet) { + // use default sign opts if osx-sign is true, otherwise clone osx-sign object + const signOpts = properties === true ? { identity: null } : { ...properties } + + // osx-sign options are handed off to sign module, but + // with a few additions from the main options + // user may think they can pass platform, app, or version, but they will be ignored + common.subOptionWarning(signOpts, 'osx-sign', 'platform', platform, quiet) + common.subOptionWarning(signOpts, 'osx-sign', 'app', app, quiet) + common.subOptionWarning(signOpts, 'osx-sign', 'version', version, quiet) + + if (signOpts.binaries) { + common.warning('osx-sign.binaries is not an allowed sub-option. Not passing to @electron/osx-sign.', quiet) + delete signOpts.binaries + } + + // Take argument osx-sign as signing identity: + // if opts.osxSign is true (bool), fallback to identity=null for + // autodiscovery. Otherwise, provide signing certificate info. + if (signOpts.identity === true) { + signOpts.identity = null + } + + return signOpts +} + +function createNotarizeOpts (properties, appBundleId, appPath, quiet) { + // osxNotarize options are handed off to the @electron/notarize module, but with a few + // additions from the main options. The user may think they can pass bundle ID or appPath, + // but they will be ignored. + if (properties.tool !== 'notarytool') { + common.subOptionWarning(properties, 'osxNotarize', 'appBundleId', appBundleId, quiet) + } + common.subOptionWarning(properties, 'osxNotarize', 'appPath', appPath, quiet) + return properties +} + +module.exports = { + App: MacApp, + createNotarizeOpts: createNotarizeOpts, + createSignOpts: createSignOpts, + filterCFBundleIdentifier: filterCFBundleIdentifier +} diff --git a/desktop/node_modules/electron-packager/src/platform.js b/desktop/node_modules/electron-packager/src/platform.js new file mode 100644 index 0000000..18df14f --- /dev/null +++ b/desktop/node_modules/electron-packager/src/platform.js @@ -0,0 +1,277 @@ +'use strict' + +const asar = require('@electron/asar') +const crypto = require('crypto') +const debug = require('debug')('electron-packager') +const fs = require('fs-extra') +const path = require('path') + +const common = require('./common') +const copyFilter = require('./copy-filter') +const hooks = require('./hooks') + +class App { + constructor (opts, templatePath) { + this.opts = opts + this.templatePath = templatePath + this.asarOptions = common.createAsarOpts(opts) + + if (this.opts.prune === undefined) { + this.opts.prune = true + } + } + + /** + * Resource directory path before renaming. + */ + get originalResourcesDir () { + return this.resourcesDir + } + + /** + * Resource directory path after renaming. + */ + get resourcesDir () { + return path.join(this.stagingPath, 'resources') + } + + get originalResourcesAppDir () { + return path.join(this.originalResourcesDir, 'app') + } + + get electronBinaryDir () { + return this.stagingPath + } + + get originalElectronName () { + /* istanbul ignore next */ + throw new Error('Child classes must implement this') + } + + get newElectronName () { + /* istanbul ignore next */ + throw new Error('Child classes must implement this') + } + + get executableName () { + return this.opts.executableName || this.opts.name + } + + get stagingPath () { + if (this.opts.tmpdir === false) { + return common.generateFinalPath(this.opts) + } else { + if (!this.cachedStagingPath) { + const parentDir = path.join( + common.baseTempDir(this.opts), + `${this.opts.platform}-${this.opts.arch}` + ) + fs.mkdirpSync(parentDir) + this.cachedStagingPath = fs.mkdtempSync(path.join(parentDir, `${common.generateFinalBasename(this.opts)}-`)) + } + return this.cachedStagingPath + } + } + + get appAsarPath () { + return path.join(this.originalResourcesDir, 'app.asar') + } + + get commonHookArgs () { + return [ + this.opts.electronVersion, + this.opts.platform, + this.opts.arch + ] + } + + get hookArgsWithOriginalResourcesAppDir () { + return [ + this.originalResourcesAppDir, + ...this.commonHookArgs + ] + } + + async relativeRename (basePath, oldName, newName) { + debug(`Renaming ${oldName} to ${newName} in ${basePath}`) + await fs.rename(path.join(basePath, oldName), path.join(basePath, newName)) + } + + async renameElectron () { + return this.relativeRename(this.electronBinaryDir, this.originalElectronName, this.newElectronName) + } + + /** + * Performs the following initial operations for an app: + * * Creates temporary directory + * * Remove default_app (which is either a folder or an asar file) + * * If a prebuilt asar is specified: + * * Copies asar into temporary directory as app.asar + * * Otherwise: + * * Copies template into temporary directory + * * Copies user's app into temporary directory + * * Prunes non-production node_modules (if opts.prune is either truthy or undefined) + * * Creates an asar (if opts.asar is set) + * + * Prune and asar are performed before platform-specific logic, primarily so that + * this.originalResourcesAppDir is predictable (e.g. before .app is renamed for Mac) + */ + async initialize () { + debug(`Initializing app in ${this.stagingPath} from ${this.templatePath} template`) + + await fs.move(this.templatePath, this.stagingPath, { clobber: true }) + await this.removeDefaultApp() + if (this.opts.prebuiltAsar) { + await this.copyPrebuiltAsar() + } else { + await this.buildApp() + } + + await hooks.promisifyHooks(this.opts.afterInitialize, this.hookArgsWithOriginalResourcesAppDir) + } + + async buildApp () { + await this.copyTemplate() + await common.validateElectronApp(this.opts.dir, this.originalResourcesAppDir) + await this.asarApp() + } + + async copyTemplate () { + await hooks.promisifyHooks(this.opts.beforeCopy, this.hookArgsWithOriginalResourcesAppDir) + + await fs.copy(this.opts.dir, this.originalResourcesAppDir, { + filter: copyFilter.userPathFilter(this.opts), + dereference: this.opts.derefSymlinks + }) + await hooks.promisifyHooks(this.opts.afterCopy, this.hookArgsWithOriginalResourcesAppDir) + if (this.opts.prune) { + await hooks.promisifyHooks(this.opts.afterPrune, this.hookArgsWithOriginalResourcesAppDir) + } + } + + async removeDefaultApp () { + await Promise.all(['default_app', 'default_app.asar'].map(async basename => fs.remove(path.join(this.originalResourcesDir, basename)))) + } + + /** + * Forces an icon filename to a given extension and returns the normalized filename, + * if it exists. Otherwise, returns null. + * + * This error path is used by win32 if no icon is specified. + */ + async normalizeIconExtension (targetExt) { + if (!this.opts.icon) throw new Error('No filename specified to normalizeIconExtension') + + let iconFilename = this.opts.icon + const ext = path.extname(iconFilename) + if (ext !== targetExt) { + iconFilename = path.join(path.dirname(iconFilename), path.basename(iconFilename, ext) + targetExt) + } + + if (await fs.pathExists(iconFilename)) { + return iconFilename + } else { + /* istanbul ignore next */ + common.warning(`Could not find icon "${iconFilename}", not updating app icon`, this.opts.quiet) + } + } + + prebuiltAsarWarning (option, triggerWarning) { + if (triggerWarning) { + common.warning(`prebuiltAsar and ${option} are incompatible, ignoring the ${option} option`, this.opts.quiet) + } + } + + async copyPrebuiltAsar () { + if (this.asarOptions) { + common.warning('prebuiltAsar has been specified, all asar options will be ignored', this.opts.quiet) + } + + for (const hookName of ['beforeCopy', 'afterCopy', 'afterPrune']) { + if (this.opts[hookName]) { + throw new Error(`${hookName} is incompatible with prebuiltAsar`) + } + } + + this.prebuiltAsarWarning('ignore', this.opts.originalIgnore) + this.prebuiltAsarWarning('prune', !this.opts.prune) + this.prebuiltAsarWarning('derefSymlinks', this.opts.derefSymlinks !== undefined) + + const src = path.resolve(this.opts.prebuiltAsar) + + const stat = await fs.stat(src) + if (!stat.isFile()) { + throw new Error(`${src} specified in prebuiltAsar must be an asar file.`) + } + + debug(`Copying asar: ${src} to ${this.appAsarPath}`) + await fs.copy(src, this.appAsarPath, { overwrite: false, errorOnExist: true }) + } + + appRelativePath (p) { + return path.relative(this.stagingPath, p) + } + + async asarApp () { + if (!this.asarOptions) { + return Promise.resolve() + } + + debug(`Running asar with the options ${JSON.stringify(this.asarOptions)}`) + + await hooks.promisifyHooks(this.opts.beforeAsar, this.hookArgsWithOriginalResourcesAppDir) + + await asar.createPackageWithOptions(this.originalResourcesAppDir, this.appAsarPath, this.asarOptions) + const { headerString } = asar.getRawHeader(this.appAsarPath) + this.asarIntegrity = { + [this.appRelativePath(this.appAsarPath)]: { + algorithm: 'SHA256', + hash: crypto.createHash('SHA256').update(headerString).digest('hex') + } + } + await fs.remove(this.originalResourcesAppDir) + + await hooks.promisifyHooks(this.opts.afterAsar, this.hookArgsWithOriginalResourcesAppDir) + } + + async copyExtraResources () { + if (!this.opts.extraResource) return Promise.resolve() + + const extraResources = common.ensureArray(this.opts.extraResource) + + const hookArgs = [ + this.stagingPath, + ...this.commonHookArgs + ] + + await hooks.promisifyHooks(this.opts.beforeCopyExtraResources, hookArgs) + + await Promise.all(extraResources.map( + resource => fs.copy(resource, path.resolve(this.stagingPath, this.resourcesDir, path.basename(resource))) + )) + + await hooks.promisifyHooks(this.opts.afterCopyExtraResources, hookArgs) + } + + async move () { + const finalPath = common.generateFinalPath(this.opts) + + if (this.opts.tmpdir !== false) { + debug(`Moving ${this.stagingPath} to ${finalPath}`) + await fs.move(this.stagingPath, finalPath) + } + + if (this.opts.afterComplete) { + const hookArgs = [ + finalPath, + ...this.commonHookArgs + ] + + await hooks.promisifyHooks(this.opts.afterComplete, hookArgs) + } + + return finalPath + } +} + +module.exports = App diff --git a/desktop/node_modules/electron-packager/src/prune.js b/desktop/node_modules/electron-packager/src/prune.js new file mode 100644 index 0000000..a94f49a --- /dev/null +++ b/desktop/node_modules/electron-packager/src/prune.js @@ -0,0 +1,70 @@ +'use strict' + +const common = require('./common') +const galactus = require('galactus') +const fs = require('fs-extra') +const path = require('path') + +const ELECTRON_MODULES = [ + 'electron', + 'electron-nightly', + 'electron-prebuilt', + 'electron-prebuilt-compile' +] + +class Pruner { + constructor (dir, quiet) { + this.baseDir = common.normalizePath(dir) + this.quiet = quiet + this.galactus = new galactus.DestroyerOfModules({ + rootDirectory: dir, + shouldKeepModuleTest: (module, isDevDep) => this.shouldKeepModule(module, isDevDep) + }) + this.walkedTree = false + } + + setModules (moduleMap) { + const modulePaths = Array.from(moduleMap.keys()).map(modulePath => `/${common.normalizePath(modulePath)}`) + this.modules = new Set(modulePaths) + this.walkedTree = true + } + + async pruneModule (name) { + if (this.walkedTree) { + return this.isProductionModule(name) + } else { + const moduleMap = await this.galactus.collectKeptModules({ relativePaths: true }) + this.setModules(moduleMap) + return this.isProductionModule(name) + } + } + + shouldKeepModule (module, isDevDep) { + if (isDevDep || module.depType === galactus.DepType.ROOT) { + return false + } + + if (ELECTRON_MODULES.includes(module.name)) { + common.warning(`Found '${module.name}' but not as a devDependency, pruning anyway`, this.quiet) + return false + } + + return true + } + + isProductionModule (name) { + return this.modules.has(name) + } +} + +function isNodeModuleFolder (pathToCheck) { + return path.basename(path.dirname(pathToCheck)) === 'node_modules' || + (path.basename(path.dirname(pathToCheck)).startsWith('@') && path.basename(path.resolve(pathToCheck, `..${path.sep}..`)) === 'node_modules') +} + +module.exports = { + isModule: async function isModule (pathToCheck) { + return (await fs.pathExists(path.join(pathToCheck, 'package.json'))) && isNodeModuleFolder(pathToCheck) + }, + Pruner: Pruner +} diff --git a/desktop/node_modules/electron-packager/src/targets.js b/desktop/node_modules/electron-packager/src/targets.js new file mode 100644 index 0000000..aba6cc0 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/targets.js @@ -0,0 +1,149 @@ +'use strict' + +const common = require('./common') +const { getHostArch } = require('@electron/get') +const semver = require('semver') + +const officialArchs = ['ia32', 'x64', 'armv7l', 'arm64', 'mips64el', 'universal'] +const officialPlatforms = ['darwin', 'linux', 'mas', 'win32'] +const officialPlatformArchCombos = { + darwin: ['x64', 'arm64', 'universal'], + linux: ['ia32', 'x64', 'armv7l', 'arm64', 'mips64el'], + mas: ['x64', 'arm64', 'universal'], + win32: ['ia32', 'x64', 'arm64'] +} + +const buildVersions = { + darwin: { + arm64: '>= 11.0.0-beta.1', + universal: '>= 11.0.0-beta.1' + }, + linux: { + arm64: '>= 1.8.0', + ia32: '<19.0.0-beta.1', + mips64el: '^1.8.2-beta.5' + }, + mas: { + arm64: '>= 11.0.0-beta.1', + universal: '>= 11.0.0-beta.1' + }, + win32: { + arm64: '>= 6.0.8' + } +} + +// Maps to module filename for each platform (lazy-required if used) +const osModules = { + darwin: './mac', + linux: './linux', + mas: './mac', // map to darwin + win32: './win32' +} + +const supported = { + arch: new Set(officialArchs), + platform: new Set(officialPlatforms) +} + +function createPlatformArchPairs (opts, selectedPlatforms, selectedArchs, ignoreFunc) { + const combinations = [] + for (const arch of selectedArchs) { + for (const platform of selectedPlatforms) { + if (usingOfficialElectronPackages(opts)) { + if (!validOfficialPlatformArch(opts, platform, arch)) { + warnIfAllNotSpecified(opts, `The platform/arch combination ${platform}/${arch} is not currently supported by Electron Packager`) + continue + } else if (buildVersions[platform] && buildVersions[platform][arch]) { + const buildVersion = buildVersions[platform][arch] + if (buildVersion && !officialBuildExists(opts, buildVersion)) { + warnIfAllNotSpecified(opts, `Official ${platform}/${arch} support only exists in Electron ${buildVersion}`) + continue + } + } + if (typeof ignoreFunc === 'function' && ignoreFunc(platform, arch)) continue + } + combinations.push([platform, arch]) + } + } + + return combinations +} + +function unsupportedListOption (name, value, supported) { + return new Error(`Unsupported ${name}=${value} (${typeof value}); must be a string matching: ${Array.from(supported.values()).join(', ')}`) +} + +function usingOfficialElectronPackages (opts) { + return !opts.download || !Object.prototype.hasOwnProperty.call(opts.download, 'mirrorOptions') +} + +function validOfficialPlatformArch (opts, platform, arch) { + return officialPlatformArchCombos[platform] && officialPlatformArchCombos[platform].includes(arch) +} + +function officialBuildExists (opts, buildVersion) { + return semver.satisfies(opts.electronVersion, buildVersion, { includePrerelease: true }) +} + +function allPlatformsOrArchsSpecified (opts) { + return opts.all || opts.arch === 'all' || opts.platform === 'all' +} + +function warnIfAllNotSpecified (opts, message) { + if (!allPlatformsOrArchsSpecified(opts)) { + common.warning(message, opts.quiet) + } +} + +module.exports = { + allOfficialArchsForPlatformAndVersion: function allOfficialArchsForPlatformAndVersion (platform, electronVersion) { + const archs = officialPlatformArchCombos[platform] + if (buildVersions[platform]) { + const excludedArchs = Object.keys(buildVersions[platform]) + .filter(arch => !officialBuildExists({ electronVersion: electronVersion }, buildVersions[platform][arch])) + return archs.filter(arch => !excludedArchs.includes(arch)) + } + + return archs + }, + createPlatformArchPairs, + officialArchs, + officialPlatformArchCombos, + officialPlatforms, + osModules, + supported, + // Validates list of architectures or platforms. + // Returns a normalized array if successful, or throws an Error. + validateListFromOptions: function validateListFromOptions (opts, name) { + if (opts.all) return Array.from(supported[name].values()) + + let list = opts[name] + if (!list) { + if (name === 'arch') { + list = getHostArch() + } else { + list = process[name] + } + } else if (list === 'all') { + return Array.from(supported[name].values()) + } + + if (!Array.isArray(list)) { + if (typeof list === 'string') { + list = list.split(/,\s*/) + } else { + return unsupportedListOption(name, list, supported[name]) + } + } + + const officialElectronPackages = usingOfficialElectronPackages(opts) + + for (const value of list) { + if (officialElectronPackages && !supported[name].has(value)) { + return unsupportedListOption(name, value, supported[name]) + } + } + + return list + } +} diff --git a/desktop/node_modules/electron-packager/src/universal.js b/desktop/node_modules/electron-packager/src/universal.js new file mode 100644 index 0000000..8ae7cb1 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/universal.js @@ -0,0 +1,80 @@ +'use strict' + +const universal = require('@electron/universal') +const common = require('./common') +const fs = require('fs-extra') +const path = require('path') + +async function packageUniversalMac (packageForPlatformAndArchWithOpts, buildDir, comboOpts, downloadOpts, tempBase) { + // In order to generate a universal macOS build we actually need to build the x64 and the arm64 app + // and then glue them together + common.info(`Packaging app for platform ${comboOpts.platform} universal using electron v${comboOpts.electronVersion} - Building x64 and arm64 slices now`, comboOpts.quiet) + await fs.mkdirp(tempBase) + const tempDir = await fs.mkdtemp(path.resolve(tempBase, 'electron-packager-universal-')) + + const { App } = require('./mac') + const app = new App(comboOpts, buildDir) + const universalStagingPath = app.stagingPath + const finalUniversalPath = common.generateFinalPath(app.opts) + + if (await fs.pathExists(finalUniversalPath)) { + if (comboOpts.overwrite) { + await fs.remove(finalUniversalPath) + } else { + common.info(`Skipping ${comboOpts.platform} ${comboOpts.arch} (output dir already exists, use --overwrite to force)`, comboOpts.quiet) + return true + } + } + + const tempPackages = {} + + for (const tempArch of ['x64', 'arm64']) { + const tempOpts = { + ...comboOpts, + arch: tempArch, + out: tempDir + } + const tempDownloadOpts = { + ...downloadOpts, + arch: tempArch + } + // Do not sign or notarize the individual slices, we sign and notarize the merged app later + delete tempOpts.osxSign + delete tempOpts.osxNotarize + + tempPackages[tempArch] = await packageForPlatformAndArchWithOpts(tempOpts, tempDownloadOpts) + } + + const x64AppPath = tempPackages.x64 + const arm64AppPath = tempPackages.arm64 + + common.info(`Stitching universal app for platform ${comboOpts.platform}`, comboOpts.quiet) + + const generatedFiles = await fs.readdir(x64AppPath) + const appName = generatedFiles.filter(file => path.extname(file) === '.app')[0] + + await universal.makeUniversalApp({ + ...comboOpts.osxUniversal, + x64AppPath: path.resolve(x64AppPath, appName), + arm64AppPath: path.resolve(arm64AppPath, appName), + outAppPath: path.resolve(universalStagingPath, appName) + }) + + await app.signAppIfSpecified() + await app.notarizeAppIfSpecified() + await app.move() + + for (const generatedFile of generatedFiles) { + if (path.extname(generatedFile) === '.app') continue + + await fs.copy(path.resolve(x64AppPath, generatedFile), path.resolve(finalUniversalPath, generatedFile)) + } + + await fs.remove(tempDir) + + return finalUniversalPath +} + +module.exports = { + packageUniversalMac +} diff --git a/desktop/node_modules/electron-packager/src/unzip.js b/desktop/node_modules/electron-packager/src/unzip.js new file mode 100644 index 0000000..705c552 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/unzip.js @@ -0,0 +1,7 @@ +'use strict' + +const extractZip = require('extract-zip') + +module.exports = async function extractElectronZip (zipPath, targetDir) { + await extractZip(zipPath, { dir: targetDir }) +} diff --git a/desktop/node_modules/electron-packager/src/win32.js b/desktop/node_modules/electron-packager/src/win32.js new file mode 100644 index 0000000..9416617 --- /dev/null +++ b/desktop/node_modules/electron-packager/src/win32.js @@ -0,0 +1,113 @@ +'use strict' + +const debug = require('debug')('electron-packager') +const path = require('path') +const { WrapperError } = require('cross-spawn-windows-exe') + +const App = require('./platform') +const common = require('./common') + +function updateWineMissingException (err) { + if (err instanceof WrapperError) { + err.message += '\n\n' + + 'Wine is required to use the appCopyright, appVersion, buildVersion, icon, and \n' + + 'win32metadata parameters for Windows targets.\n\n' + + 'See https://github.com/electron/electron-packager#building-windows-apps-from-non-windows-platforms for details.' + } + + return err +} + +class WindowsApp extends App { + get originalElectronName () { + return 'electron.exe' + } + + get newElectronName () { + return `${common.sanitizeAppName(this.executableName)}.exe` + } + + get electronBinaryPath () { + return path.join(this.stagingPath, this.newElectronName) + } + + generateRceditOptionsSansIcon () { + const win32metadata = { + FileDescription: this.opts.name, + InternalName: this.opts.name, + OriginalFilename: this.newElectronName, + ProductName: this.opts.name, + ...this.opts.win32metadata + } + + const rcOpts = { 'version-string': win32metadata } + + if (this.opts.appVersion) { + rcOpts['product-version'] = rcOpts['file-version'] = this.opts.appVersion + } + + if (this.opts.buildVersion) { + rcOpts['file-version'] = this.opts.buildVersion + } + + if (this.opts.appCopyright) { + rcOpts['version-string'].LegalCopyright = this.opts.appCopyright + } + + const manifestProperties = ['application-manifest', 'requested-execution-level'] + for (const manifestProperty of manifestProperties) { + if (win32metadata[manifestProperty]) { + rcOpts[manifestProperty] = win32metadata[manifestProperty] + } + } + + return rcOpts + } + + async getIconPath () { + if (!this.opts.icon) { + return Promise.resolve() + } + + return this.normalizeIconExtension('.ico') + } + + needsRcedit () { + return this.opts.icon || this.opts.win32metadata || this.opts.appCopyright || this.opts.appVersion || this.opts.buildVersion + } + + async runRcedit () { + /* istanbul ignore if */ + if (!this.needsRcedit()) { + return Promise.resolve() + } + + const rcOpts = this.generateRceditOptionsSansIcon() + + // Icon might be omitted or only exist in one OS's format, so skip it if normalizeExt reports an error + const icon = await this.getIconPath() + if (icon) { + rcOpts.icon = icon + } + + debug(`Running rcedit with the options ${JSON.stringify(rcOpts)}`) + try { + await require('rcedit')(this.electronBinaryPath, rcOpts) + } catch (err) { + throw updateWineMissingException(err) + } + } + + async create () { + await this.initialize() + await this.renameElectron() + await this.copyExtraResources() + await this.runRcedit() + return this.move() + } +} + +module.exports = { + App: WindowsApp, + updateWineMissingException: updateWineMissingException +} |