mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
* 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
150 lines
5.1 KiB
TypeScript
150 lines
5.1 KiB
TypeScript
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
|
|
});
|
|
});
|
|
});
|
|
}
|