Repository: electron-userland/electron-builder
File: packages/electron-updater/src/BaseUpdater.ts (TypeScript)

Code before:
      },
    })
  }

  // must be sync
  protected abstract doInstall(options: InstallOptions): boolean

  // must be sync (because quit even handler is not async)
  install(isSilent = false, isForceRunAfter = false): boolean {
    if (this.quitAndInstallCalled) {
      this._logger.warn("install call ignored: quitAndInstallCalled is set to true")
      return false
    }

    const downloadedUpdateHelper = this.downloadedUpdateHelper
    const installerPath = downloadedUpdateHelper == null ? null : downloadedUpdateHelper.file
    const downloadedFileInfo = downloadedUpdateHelper == null ? null : downloadedUpdateHelper.downloadedFileInfo
    if (installerPath == null || downloadedFileInfo == null) {
      this.dispatchError(new Error("No valid update available, can't quit and install"))
      return false
    }

    // prevent calling several times
    this.quitAndInstallCalled = true

    try {
      this._logger.info(`Install: isSilent: ${isSilent}, isForceRunAfter: ${isForceRunAfter}`)
      return this.doInstall({
        installerPath,
        isSilent,
        isForceRunAfter,
        isAdminRightsRequired: downloadedFileInfo.isAdminRightsRequired,
      })
    } catch (e: any) {
      this.dispatchError(e)
      return false
    }
  }

  protected addQuitHandler(): void {
    if (this.quitHandlerAdded || !this.autoInstallOnAppQuit) {
      return
    }

    this.quitHandlerAdded = true

    this.app.onQuit(exitCode => {
      if (this.quitAndInstallCalled) {
        this._logger.info("Update installer has already been triggered. Quitting application.")
        return
      }

Review comment (suggestion):
Will this be an issue for AppImages? Would this break previous functionality with the `emit` logic?
https://github.com/electron-userland/electron-builder/blob/12c52a81420f04ec0e205dd83798c2b0b773011d/packages/electron-updater/src/AppImageUpdater.ts#L89-L96

Code after:
      },
    })
  }

  // must be sync
  protected abstract doInstall(options: InstallOptions): boolean

  // must be sync (because quit even handler is not async)
  install(isSilent = false, isForceRunAfter = false): boolean {
    if (this.quitAndInstallCalled) {
      this._logger.warn("install call ignored: quitAndInstallCalled is set to true")
      return false
    }

    const downloadedUpdateHelper = this.downloadedUpdateHelper
    
    // Get the installer path, ensuring spaces are escaped on Linux
    // 1. Check if downloadedUpdateHelper is not null
    // 2. Check if downloadedUpdateHelper.file is not null
    // 3. If both checks pass:
    //    a. If the platform is Linux, replace spaces with '\ ' for shell compatibility
    //    b. If the platform is not Linux, use the original path
    // 4. If any check fails, set installerPath to null
    const installerPath = downloadedUpdateHelper && downloadedUpdateHelper.file 
      ? (process.platform === 'linux' 
        ? downloadedUpdateHelper.file.replace(/ /g, '\\ ') 
        : downloadedUpdateHelper.file) 
      : null;

    const downloadedFileInfo = downloadedUpdateHelper == null ? null : downloadedUpdateHelper.downloadedFileInfo
    if (installerPath == null || downloadedFileInfo == null) {
      this.dispatchError(new Error("No valid update available, can't quit and install"))
      return false
    }

    // prevent calling several times
    this.quitAndInstallCalled = true

    try {
      this._logger.info(`Install: isSilent: ${isSilent}, isForceRunAfter: ${isForceRunAfter}`)
      return this.doInstall({
        installerPath,
        isSilent,
        isForceRunAfter,
        isAdminRightsRequired: downloadedFileInfo.isAdminRightsRequired,
      })
    } catch (e: any) {
      this.dispatchError(e)
      return false
    }
  }