diff --git a/.changeset/gorgeous-poems-joke.md b/.changeset/gorgeous-poems-joke.md new file mode 100644 index 00000000000..7f24dc1c9f5 --- /dev/null +++ b/.changeset/gorgeous-poems-joke.md @@ -0,0 +1,5 @@ +--- +"electron-updater": patch +--- + +fix: auto-update powershell script requires reset of `PSModulePath` diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 61325416301..9cb5577e682 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,14 +8,9 @@ on: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: - inputs: - build-docker-locally: - type: boolean - description: Force rebuild docker images for CI tests - required: false permissions: - contents: read # to fetch code (actions/checkout) + contents: read jobs: test-linux: @@ -55,7 +50,7 @@ jobs: env: TEST_FILES: ${{ matrix.testFiles }} FORCE_COLOR: 1 - + test-mac: runs-on: macos-latest steps: @@ -73,7 +68,7 @@ jobs: env: TEST_FILES: masTest,dmgTest,filesTest,macPackagerTest FORCE_COLOR: 1 - + # Need to separate from other tests because logic is specific to when TOKEN env vars are set test-updater: runs-on: ubuntu-latest @@ -101,8 +96,9 @@ jobs: strategy: matrix: testFiles: + - winCodeSignTest - installerTest,appxTest,msiTest,portableTest,assistedInstallerTest,protonTest - - BuildTest,oneClickInstallerTest,winCodeSignTest,winPackagerTest,webInstallerTest + - BuildTest,oneClickInstallerTest,winPackagerTest,nsisUpdaterTest,webInstallerTest steps: - name: Checkout code repository uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4 @@ -112,7 +108,7 @@ jobs: with: cache-key: v-17.0.0-windows-electron cache-path: ~\AppData\Local\electron\Cache - + - name: Test run: pnpm ci:test env: diff --git a/package.json b/package.json index 6d53069e54f..51b88c73885 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,7 @@ "ci:publish": "pnpm compile && pnpm publish -r && changeset tag", "jsdoc": "ts2jsdoc packages/builder-util-runtime packages/builder-util packages/app-builder-lib packages/electron-builder packages/electron-publish packages/electron-updater packages/dmg-builder", "jsdoc2md2html": "node scripts/jsdoc2md2html.js", - "prepare": "husky install", - "update-deps": "pnpm update -i -r --latest" + "prepare": "husky install" }, "//": "repository must be specified otherwise conventional-changelog will use forked repo (currently cloned)", "repository": "https://github.com/electron-userland/electron-builder", diff --git a/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts b/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts index c676ecf26c1..69e1e40e8cc 100644 --- a/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts +++ b/packages/app-builder-lib/src/targets/nsis/NsisTarget.ts @@ -397,7 +397,7 @@ export class NsisTarget extends Target { } else { await execWine(installerPath, null, [], { env: { __COMPAT_LAYER: "RunAsInvoker" } }) } - await packager.sign(uninstallerPath, " Signing NSIS uninstaller") + await packager.sign(uninstallerPath, "signing NSIS uninstaller") delete defines.BUILD_UNINSTALLER // platform-specific path, not wine diff --git a/packages/electron-updater/src/differentialDownloader/downloadPlanBuilder.ts b/packages/electron-updater/src/differentialDownloader/downloadPlanBuilder.ts index 7669aa3e7f7..c4d6f178596 100644 --- a/packages/electron-updater/src/differentialDownloader/downloadPlanBuilder.ts +++ b/packages/electron-updater/src/differentialDownloader/downloadPlanBuilder.ts @@ -25,7 +25,7 @@ export function computeOperations(oldBlockMap: BlockMap, newBlockMap: BlockMap, let lastOperation: Operation | null = null // for now only one file is supported in block map - const blockMapFile = newBlockMap.files[0] + const blockMapFile: { name: string; offset: number } = newBlockMap.files[0] const operations: Array = [] const name = blockMapFile.name const oldEntry = nameToOldBlocks.get(name) @@ -41,7 +41,7 @@ export function computeOperations(oldBlockMap: BlockMap, newBlockMap: BlockMap, let newOffset = blockMapFile.offset for (let i = 0; i < newFile.checksums.length; newOffset += newFile.sizes[i], i++) { - const blockSize = newFile.sizes[i] + const blockSize: number = newFile.sizes[i] const checksum = newFile.checksums[i] let oldOffset = checksumToOldOffset.get(checksum) if (oldOffset != null && checksumToOldSize.get(checksum) !== blockSize) { diff --git a/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts b/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts index 77e5f63bcfe..ab52ef6e809 100644 --- a/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts +++ b/packages/electron-updater/src/windowsExecutableCodeSignatureVerifier.ts @@ -27,11 +27,13 @@ export function verifySignature(publisherNames: Array, unescapedTempUpda // guaranteed that the path will not contain any illegal characters like <>:"/\|?* // https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file const tempUpdateFile = unescapedTempUpdateFile.replace(/'/g, "''") + logger.info(`Verifying signature ${tempUpdateFile}`) // https://github.com/electron-userland/electron-builder/issues/2421 // https://github.com/electron-userland/electron-builder/issues/2535 + // Resetting PSModulePath is necessary https://github.com/electron-userland/electron-builder/issues/7127 execFile( - "chcp 65001 >NUL & powershell.exe", + `set "PSModulePath="; chcp 65001 >NUL & powershell.exe`, ["-NoProfile", "-NonInteractive", "-InputFormat", "None", "-Command", `"Get-AuthenticodeSignature -LiteralPath '${tempUpdateFile}' | ConvertTo-Json -Compress"`], { shell: true, @@ -44,7 +46,6 @@ export function verifySignature(publisherNames: Array, unescapedTempUpda resolve(null) return } - const data = parseOut(stdout) if (data.Status === 0) { const subject = parseDn(data.SignerCertificate.Subject) diff --git a/test/snapshots/updater/nsisUpdaterTest.js.snap b/test/snapshots/updater/nsisUpdaterTest.js.snap index 1c37a508194..a4d542c3f8a 100644 --- a/test/snapshots/updater/nsisUpdaterTest.js.snap +++ b/test/snapshots/updater/nsisUpdaterTest.js.snap @@ -471,6 +471,39 @@ Array [ ] `; +exports[`test custom signature verifier - signing error message 1`] = `"ERR_UPDATER_INVALID_SIGNATURE"`; + +exports[`test custom signature verifier - signing error message 2`] = ` +Array [ + "checking-for-update", + "update-available", + "error", +] +`; + +exports[`test custom signature verifier 1`] = ` +Object { + "files": Array [ + Object { + "sha512": "xrTrW8dzWYlPnu71Y4lpLIAuIurBZJvZmqEZyz1rzM3CbbE1Z+T+P5qYYZgwmhmXdYPOpvnmYKa0HGdgXggwtQ==", + "url": "TestApp-Setup-1.1.0.exe", + }, + ], + "releaseName": "1.1.0", + "releaseNotes": "", + "tag": "v1.1.0", + "version": "1.1.0", +} +`; + +exports[`test custom signature verifier 2`] = ` +Array [ + "checking-for-update", + "update-available", + "update-downloaded", +] +`; + exports[`test download and install 1`] = ` Object { "files": Array [ diff --git a/test/src/updater/nsisUpdaterTest.ts b/test/src/updater/nsisUpdaterTest.ts index 1626fc8523d..d225d1e3ee6 100644 --- a/test/src/updater/nsisUpdaterTest.ts +++ b/test/src/updater/nsisUpdaterTest.ts @@ -186,6 +186,7 @@ test("file url github", async () => { updater.updateConfigPath = await writeUpdateConfig(options) updater.signals.updateDownloaded(info => { expect(info.downloadedFile).not.toBeNull() + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion delete (info as any).downloadedFile expect(info).toMatchSnapshot() }) @@ -203,6 +204,7 @@ test("file url github pre-release and fullChangelog", async () => { updater.updateConfigPath = await writeUpdateConfig(options) updater.signals.updateDownloaded(info => { expect(info.downloadedFile).not.toBeNull() + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion delete (info as any).downloadedFile expect(info).toMatchSnapshot() }) @@ -284,10 +286,11 @@ test.ifAll("valid signature using DN", async () => { repo: "__test_nsis_release", publisherName: ["CN=Vladimir Krivosheev, O=Vladimir Krivosheev, L=Grunwald, S=Bayern, C=DE"], }) + await validateDownload(updater) }) -test.skip.ifAll("invalid signature", async () => { +test.ifWindows("invalid signature", async () => { const updater = await createNsisUpdater("0.0.1") updater.updateConfigPath = await writeUpdateConfig({ provider: "github", @@ -300,6 +303,36 @@ test.skip.ifAll("invalid signature", async () => { expect(actualEvents).toMatchSnapshot() }) +test.ifWindows("test custom signature verifier", async () => { + const updater = await createNsisUpdater("1.0.2") + updater.updateConfigPath = await writeUpdateConfig({ + provider: "github", + owner: "develar", + repo: "__test_nsis_release", + publisherName: ["CN=Vladimir Krivosheev, O=Vladimir Krivosheev, L=Grunwald, S=Bayern, C=DE"], + }) + updater.verifyUpdateCodeSignature = (publisherName: string[], path: string) => { + return Promise.resolve(null) + } + await validateDownload(updater) +}) + +test.ifWindows("test custom signature verifier - signing error message", async () => { + const updater = await createNsisUpdater("1.0.2") + updater.updateConfigPath = await writeUpdateConfig({ + provider: "github", + owner: "develar", + repo: "__test_nsis_release", + publisherName: ["CN=Vladimir Krivosheev, O=Vladimir Krivosheev, L=Grunwald, S=Bayern, C=DE"], + }) + updater.verifyUpdateCodeSignature = (publisherName: string[], path: string) => { + return Promise.resolve("signature verification failed") + } + const actualEvents = trackEvents(updater) + await assertThat(updater.checkForUpdates().then((it): any => it?.downloadPromise)).throws() + expect(actualEvents).toMatchSnapshot() +}) + // disable for now test("90 staging percentage", async () => { const userIdFile = path.join(tmpdir(), "electron-updater-test", "userData", ".updaterId") @@ -365,21 +398,19 @@ test.ifAll("test download and install", async () => { }) await validateDownload(updater) - - const actualEvents = trackEvents(updater) - expect(actualEvents).toMatchObject([]) - // await updater.quitAndInstall(true, false) }) -test.ifAll("test downloaded installer", async () => { - const updater = await createNsisUpdater() - updater.updateConfigPath = await writeUpdateConfig({ - provider: "generic", - url: "https://develar.s3.amazonaws.com/test", +test.skip.ifWindows("test downloaded installer", async () => { + const updater = await createNsisUpdater("1.0.1") + updater.updateConfigPath = await writeUpdateConfig({ + provider: "github", + owner: "mmaietta", + repo: "electron-builder-test", }) const actualEvents = trackEvents(updater) - - expect(actualEvents).toMatchObject([]) - // await updater.quitAndInstall(true, false) + await validateDownload(updater) + // expect(actualEvents).toMatchObject(["checking-for-update", "update-available", "update-downloaded"]) + updater.quitAndInstall(true, false) + expect(actualEvents).toMatchObject(["checking-for-update", "update-available", "update-downloaded", "before-quit-for-update"]) })