mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
feat: msix auto-updater (#49230)
* feat: native auto updater for MSIX on Windows * doc: added MSIX debug documentation * fix: allow downgrade with json release file and emit update-available * test: msix auot-update tests * doc: API documentation * test: add package version validation * fix: docs typo * fix: don't allow auto-updating when using appinstaller manifest * fix: getPackageInfo interface implementation * fix: review feedback, add comment * fix: missed filename commit * fix: install test cert on demand * fix: time stamp mismatch in tests * fix: feedback - rename to MSIXPackageInfo * fix: update and reference windowsStore property * fix: remove getPackagInfo from public API * fix: type error bcause of removed API
This commit is contained in:
149
spec/lib/msix-helpers.ts
Normal file
149
spec/lib/msix-helpers.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import { expect } from 'chai';
|
||||
|
||||
import * as cp from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, '..', 'fixtures', 'api', 'autoupdater', 'msix');
|
||||
const manifestFixturePath = path.resolve(fixturesPath, 'ElectronDevAppxManifest.xml');
|
||||
const installCertScriptPath = path.resolve(fixturesPath, 'install_test_cert.ps1');
|
||||
|
||||
// Install the signing certificate for MSIX test packages to the Trusted People store
|
||||
// This is required to install self-signed MSIX packages
|
||||
export async function installMsixCertificate (): Promise<void> {
|
||||
const result = cp.spawnSync('powershell', [
|
||||
'-NoProfile',
|
||||
'-ExecutionPolicy', 'Bypass',
|
||||
'-File', installCertScriptPath
|
||||
]);
|
||||
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to install MSIX certificate: ${result.stderr.toString() || result.stdout.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we should run MSIX tests
|
||||
export const shouldRunMsixTests =
|
||||
process.platform === 'win32';
|
||||
|
||||
// Get the Electron executable path
|
||||
export function getElectronExecutable (): string {
|
||||
return process.execPath;
|
||||
}
|
||||
|
||||
// Get path to main.js fixture
|
||||
export function getMainJsFixturePath (): string {
|
||||
return path.resolve(fixturesPath, 'main.js');
|
||||
}
|
||||
|
||||
// Register executable with identity
|
||||
export async function registerExecutableWithIdentity (executablePath: string): Promise<void> {
|
||||
if (!fs.existsSync(manifestFixturePath)) {
|
||||
throw new Error(`Manifest fixture not found: ${manifestFixturePath}`);
|
||||
}
|
||||
|
||||
const executableDir = path.dirname(executablePath);
|
||||
const manifestPath = path.join(executableDir, 'AppxManifest.xml');
|
||||
const escapedManifestPath = manifestPath.replace(/'/g, "''").replace(/\\/g, '\\\\');
|
||||
const psCommand = `
|
||||
$ErrorActionPreference = 'Stop';
|
||||
try {
|
||||
Add-AppxPackage -Register '${escapedManifestPath}' -ForceUpdateFromAnyVersion
|
||||
} catch {
|
||||
Write-Error $_.Exception.Message
|
||||
exit 1
|
||||
}
|
||||
`;
|
||||
|
||||
fs.copyFileSync(manifestFixturePath, manifestPath);
|
||||
|
||||
const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', psCommand]);
|
||||
if (result.status !== 0) {
|
||||
const errorMsg = result.stderr.toString() || result.stdout.toString();
|
||||
try {
|
||||
fs.unlinkSync(manifestPath);
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
throw new Error(`Failed to register executable with identity: ${errorMsg}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister the Electron Dev MSIX package
|
||||
// This removes the sparse package registration created by registerExecutableWithIdentity
|
||||
export async function unregisterExecutableWithIdentity (): Promise<void> {
|
||||
const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', ' Get-AppxPackage Electron.Dev.MSIX | Remove-AppxPackage']);
|
||||
// Don't throw if package doesn't exist
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to unregister executable with identity: ${result.stderr.toString() || result.stdout.toString()}`);
|
||||
}
|
||||
|
||||
const electronExec = getElectronExecutable();
|
||||
const executableDir = path.dirname(electronExec);
|
||||
const manifestPath = path.join(executableDir, 'AppxManifest.xml');
|
||||
try {
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
fs.unlinkSync(manifestPath);
|
||||
}
|
||||
} catch {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
|
||||
// Get path to MSIX fixture package
|
||||
export function getMsixFixturePath (version: 'v1' | 'v2'): string {
|
||||
const filename = `HelloMSIX_${version}.msix`;
|
||||
return path.resolve(fixturesPath, filename);
|
||||
}
|
||||
|
||||
// Install MSIX package
|
||||
export async function installMsixPackage (msixPath: string): Promise<void> {
|
||||
// Use Add-AppxPackage PowerShell cmdlet
|
||||
const result = cp.spawnSync('powershell', [
|
||||
'-Command',
|
||||
`Add-AppxPackage -Path "${msixPath}" -ForceApplicationShutdown`
|
||||
]);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to install MSIX package: ${result.stderr.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall MSIX package by name
|
||||
export async function uninstallMsixPackage (name: string): Promise<void> {
|
||||
const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', `Get-AppxPackage ${name} | Remove-AppxPackage`]);
|
||||
// Don't throw if package doesn't exist
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`Failed to uninstall MSIX package: ${result.stderr.toString() || result.stdout.toString()}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Get version of installed MSIX package by name
|
||||
export async function getMsixPackageVersion (name: string): Promise<string | null> {
|
||||
const psCommand = `(Get-AppxPackage -Name '${name}').Version`;
|
||||
const result = cp.spawnSync('powershell', ['-NoProfile', '-Command', psCommand]);
|
||||
if (result.status !== 0) {
|
||||
return null;
|
||||
}
|
||||
const version = result.stdout.toString().trim();
|
||||
return version || null;
|
||||
}
|
||||
|
||||
export function spawn (cmd: string, args: string[], opts: any = {}): Promise<{ code: number, out: string }> {
|
||||
let out = '';
|
||||
const child = cp.spawn(cmd, args, opts);
|
||||
child.stdout.on('data', (chunk: Buffer) => {
|
||||
out += chunk.toString();
|
||||
});
|
||||
child.stderr.on('data', (chunk: Buffer) => {
|
||||
out += chunk.toString();
|
||||
});
|
||||
return new Promise<{ code: number, out: string }>((resolve) => {
|
||||
child.on('exit', (code, signal) => {
|
||||
expect(signal).to.equal(null);
|
||||
resolve({
|
||||
code: code!,
|
||||
out
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user