diff --git a/.changeset/giant-dryers-beg.md b/.changeset/giant-dryers-beg.md new file mode 100644 index 00000000000..517f30ba5e6 --- /dev/null +++ b/.changeset/giant-dryers-beg.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": minor +--- + +feat(msi): add fileAssociation support for MSI target diff --git a/.changeset/serious-peas-help.md b/.changeset/serious-peas-help.md new file mode 100644 index 00000000000..9dc1fc01af8 --- /dev/null +++ b/.changeset/serious-peas-help.md @@ -0,0 +1,5 @@ +--- +"app-builder-lib": major +--- + +BREAKING CHANGE: remove MSI option `iconId` diff --git a/packages/app-builder-lib/src/options/FileAssociation.ts b/packages/app-builder-lib/src/options/FileAssociation.ts index 82eb6f37532..4be4e9c62be 100644 --- a/packages/app-builder-lib/src/options/FileAssociation.ts +++ b/packages/app-builder-lib/src/options/FileAssociation.ts @@ -1,9 +1,9 @@ /** * File associations. * - * macOS (corresponds to [CFBundleDocumentTypes](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685)) and NSIS only. + * macOS (corresponds to [CFBundleDocumentTypes](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-101685)), NSIS, and MSI only. * - * On Windows works only if [nsis.perMachine](https://electron.build/configuration/configuration#NsisOptions-perMachine) is set to `true`. + * On Windows (NSIS) works only if [nsis.perMachine](https://electron.build/configuration/configuration#NsisOptions-perMachine) is set to `true`. */ export interface FileAssociation { /** @@ -29,7 +29,7 @@ export interface FileAssociation { /** * The path to icon (`.icns` for MacOS and `.ico` for Windows), relative to `build` (build resources directory). Defaults to `${firstExt}.icns`/`${firstExt}.ico` (if several extensions specified, first is used) or to application icon. * - * Not supported on Linux, file issue if need (default icon will be `x-office-document`). + * Not supported on Linux, file issue if need (default icon will be `x-office-document`). Not supported on MSI. */ readonly icon?: string | null diff --git a/packages/app-builder-lib/src/options/MsiOptions.ts b/packages/app-builder-lib/src/options/MsiOptions.ts index 4132ebdb6c1..28d6da3d9d5 100644 --- a/packages/app-builder-lib/src/options/MsiOptions.ts +++ b/packages/app-builder-lib/src/options/MsiOptions.ts @@ -23,9 +23,4 @@ export interface MsiOptions extends CommonWindowsInstallerConfiguration, TargetS * Any additional arguments to be passed to the WiX installer compiler, such as `["-ext", "WixUtilExtension"]` */ readonly additionalWixArgs?: Array | null - - /** - * The [shortcut iconId](https://wixtoolset.org/documentation/manual/v4/reference/wxs/shortcut/). Optional, by default generated using app file name. - */ - readonly iconId?: string } diff --git a/packages/app-builder-lib/src/targets/MsiTarget.ts b/packages/app-builder-lib/src/targets/MsiTarget.ts index 8624d95e13d..b6b4962c5eb 100644 --- a/packages/app-builder-lib/src/targets/MsiTarget.ts +++ b/packages/app-builder-lib/src/targets/MsiTarget.ts @@ -1,5 +1,5 @@ import BluebirdPromise from "bluebird-lst" -import { Arch, log, deepAssign } from "builder-util" +import { Arch, asArray, log, deepAssign } from "builder-util" import { UUID } from "builder-util-runtime" import { getBinFromUrl } from "../binDownload" import { walk } from "builder-util/out/fs" @@ -11,6 +11,7 @@ import * as path from "path" import { MsiOptions } from "../" import { Target } from "../core" import { DesktopShortcutCreationPolicy, FinalCommonWindowsInstallerOptions, getEffectiveOptions } from "../options/CommonWindowsInstallerConfiguration" +import { normalizeExt } from "../platformPackager" import { getTemplatePath } from "../util/pathManager" import { VmManager } from "../vm/vm" import { WineVmManager } from "../vm/WineVm" @@ -40,6 +41,22 @@ export default class MsiTarget extends Target { super("msi") } + /** + * A product-specific string that can be used in an [MSI Identifier](https://docs.microsoft.com/en-us/windows/win32/msi/identifier). + */ + private get productMsiIdPrefix() { + const sanitizedId = this.packager.appInfo.productFilename.replace(/[^\w.]/g, "").replace(/^[^A-Za-z_]+/, "") + return sanitizedId.length > 0 ? sanitizedId : "App" + this.upgradeCode.replace(/-/g, "") + } + + private get iconId() { + return `${this.productMsiIdPrefix}Icon.exe` + } + + private get upgradeCode(): string { + return (this.options.upgradeCode || UUID.v5(this.packager.appInfo.id, ELECTRON_BUILDER_UPGRADE_CODE_NS_UUID)).toUpperCase() + } + async build(appOutDir: string, arch: Arch) { const packager = this.packager const artifactName = packager.expandArtifactBeautyNamePattern(this.options, "msi", arch) @@ -156,17 +173,16 @@ export default class MsiTarget extends Target { const compression = this.packager.compression const options = this.options const iconPath = await this.packager.getIconPath() - const iconId = `${appInfo.productFilename}Icon.exe`.replace(/\s/g, "") return (await projectTemplate.value)({ ...commonOptions, isCreateDesktopShortcut: commonOptions.isCreateDesktopShortcut !== DesktopShortcutCreationPolicy.NEVER, isRunAfterFinish: options.runAfterFinish !== false, iconPath: iconPath == null ? null : this.vm.toVmFile(iconPath), - iconId: iconId, + iconId: this.iconId, compressionLevel: compression === "store" ? "none" : "high", version: appInfo.getVersionInWeirdWindowsForm(), productName: appInfo.productName, - upgradeCode: (options.upgradeCode || UUID.v5(appInfo.id, ELECTRON_BUILDER_UPGRADE_CODE_NS_UUID)).toUpperCase(), + upgradeCode: this.upgradeCode, manufacturer: companyName || appInfo.productName, appDescription: appInfo.description, // https://stackoverflow.com/questions/1929038/compilation-error-ice80-the-64bitcomponent-uses-32bitdirectory @@ -223,9 +239,8 @@ export default class MsiTarget extends Target { if (isMainExecutable && (isCreateDesktopShortcut || commonOptions.isCreateStartMenuShortcut)) { result += `>\n` const shortcutName = commonOptions.shortcutName - const iconId = `${appInfo.productFilename}Icon.exe`.replace(/\s/g, "") if (isCreateDesktopShortcut) { - result += `${fileSpace} \n` + result += `${fileSpace} \n` } const hasMenuCategory = commonOptions.menuCategory != null @@ -234,7 +249,7 @@ export default class MsiTarget extends Target { if (hasMenuCategory) { dirs.push(``) } - result += `${fileSpace} \n` + result += `${fileSpace} \n` result += `${fileSpace} \n` result += `${fileSpace} \n` } @@ -247,6 +262,22 @@ export default class MsiTarget extends Target { result += `/>` } + const fileAssociations = this.packager.fileAssociations + if (isMainExecutable && fileAssociations.length !== 0) { + for (const item of fileAssociations) { + const extensions = asArray(item.ext).map(normalizeExt) + for (const ext of extensions) { + result += `${fileSpace} \n` + result += `${fileSpace} \n` + result += `${fileSpace} \n` + result += `${fileSpace} \n` + result += `${fileSpace} \n` + } + } + } + return `${result}\n${fileSpace}` })