Skip to content

Commit

Permalink
feat(msi): add fileAssociation support for MSI target
Browse files Browse the repository at this point in the history
Also fix iconId sometimes containing invalid characters, and iconId config option being ignored.

Closes electron-userland#3694, electron-userland#6488
  • Loading branch information
aplum committed Jan 6, 2022
1 parent 67b84ad commit 4090ab6
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/giant-dryers-beg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"app-builder-lib": minor
---

feat(msi): add fileAssociation support for MSI target
6 changes: 3 additions & 3 deletions packages/app-builder-lib/src/options/FileAssociation.ts
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand All @@ -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

Expand Down
39 changes: 33 additions & 6 deletions packages/app-builder-lib/src/targets/MsiTarget.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -40,6 +41,18 @@ 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 : "ElectronApp"
}

private get iconId() {
return this.options.iconId ?? `${this.productMsiIdPrefix}Icon.exe`
}

async build(appOutDir: string, arch: Arch) {
const packager = this.packager
const artifactName = packager.expandArtifactBeautyNamePattern(this.options, "msi", arch)
Expand Down Expand Up @@ -156,13 +169,12 @@ 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,
Expand Down Expand Up @@ -223,9 +235,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} <Shortcut Id="desktopShortcut" Directory="DesktopFolder" Name="${shortcutName}" WorkingDirectory="APPLICATIONFOLDER" Advertise="yes" Icon="${iconId}"/>\n`
result += `${fileSpace} <Shortcut Id="desktopShortcut" Directory="DesktopFolder" Name="${shortcutName}" WorkingDirectory="APPLICATIONFOLDER" Advertise="yes" Icon="${this.iconId}"/>\n`
}

const hasMenuCategory = commonOptions.menuCategory != null
Expand All @@ -234,7 +245,7 @@ export default class MsiTarget extends Target {
if (hasMenuCategory) {
dirs.push(`<Directory Id="${startMenuShortcutDirectoryId}" Name="ProgramMenuFolder:\\${commonOptions.menuCategory}\\"/>`)
}
result += `${fileSpace} <Shortcut Id="startMenuShortcut" Directory="${startMenuShortcutDirectoryId}" Name="${shortcutName}" WorkingDirectory="APPLICATIONFOLDER" Advertise="yes" Icon="${iconId}">\n`
result += `${fileSpace} <Shortcut Id="startMenuShortcut" Directory="${startMenuShortcutDirectoryId}" Name="${shortcutName}" WorkingDirectory="APPLICATIONFOLDER" Advertise="yes" Icon="${this.iconId}">\n`
result += `${fileSpace} <ShortcutProperty Key="System.AppUserModel.ID" Value="${this.packager.appInfo.id}"/>\n`
result += `${fileSpace} </Shortcut>\n`
}
Expand All @@ -247,6 +258,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} <ProgId Id="${this.productMsiIdPrefix}.${ext}" Advertise="yes" Icon="${this.iconId}" ${
item.description ? `Description="${item.description}"` : ""
}>\n`
result += `${fileSpace} <Extension Id="${ext}" Advertise="yes">\n`
result += `${fileSpace} <Verb Id="open" Command="Open with ${this.packager.appInfo.productName}" Argument="&quot;%1&quot;"/>\n`
result += `${fileSpace} </Extension>\n`
result += `${fileSpace} </ProgId>\n`
}
}
}

return `${result}\n${fileSpace}</Component>`
})

Expand Down

0 comments on commit 4090ab6

Please sign in to comment.