feat: add new ElectronSquirrelPreventDowngrades flag (#38625)
* sketch * feat: add new ElectronSquirrelPreventDowngrades flag * test: remove only * chore: fix lint
This commit is contained in:
parent
16aec702b4
commit
5bff0fe342
11 changed files with 449 additions and 15 deletions
|
@ -9,7 +9,7 @@ import * as psList from 'ps-list';
|
|||
import { AddressInfo } from 'node:net';
|
||||
import { ifdescribe, ifit } from './lib/spec-helpers';
|
||||
import * as uuid from 'uuid';
|
||||
import { systemPreferences } from 'electron';
|
||||
import { autoUpdater, systemPreferences } from 'electron';
|
||||
|
||||
const features = process._linkedBinding('electron_common_features');
|
||||
|
||||
|
@ -129,11 +129,13 @@ ifdescribe(process.platform === 'darwin' && !(process.env.CI && process.arch ===
|
|||
|
||||
const cachedZips: Record<string, string> = {};
|
||||
|
||||
const getOrCreateUpdateZipPath = async (version: string, fixture: string, mutateAppPostSign?: {
|
||||
type Mutation = {
|
||||
mutate: (appPath: string) => Promise<void>,
|
||||
mutationKey: string,
|
||||
}) => {
|
||||
const key = `${version}-${fixture}-${mutateAppPostSign?.mutationKey || 'no-mutation'}`;
|
||||
};
|
||||
|
||||
const getOrCreateUpdateZipPath = async (version: string, fixture: string, mutateAppPreSign?: Mutation, mutateAppPostSign?: Mutation) => {
|
||||
const key = `${version}-${fixture}-${mutateAppPreSign?.mutationKey || 'no-pre-mutation'}-${mutateAppPostSign?.mutationKey || 'no-post-mutation'}`;
|
||||
if (!cachedZips[key]) {
|
||||
let updateZipPath: string;
|
||||
await withTempDirectory(async (dir) => {
|
||||
|
@ -143,6 +145,12 @@ ifdescribe(process.platform === 'darwin' && !(process.env.CI && process.arch ===
|
|||
appPJPath,
|
||||
(await fs.readFile(appPJPath, 'utf8')).replace('1.0.0', version)
|
||||
);
|
||||
const infoPath = path.resolve(secondAppPath, 'Contents', 'Info.plist');
|
||||
await fs.writeFile(
|
||||
infoPath,
|
||||
(await fs.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, `$1${version}`)
|
||||
);
|
||||
await mutateAppPreSign?.mutate(secondAppPath);
|
||||
await signApp(secondAppPath);
|
||||
await mutateAppPostSign?.mutate(secondAppPath);
|
||||
updateZipPath = path.resolve(dir, 'update.zip');
|
||||
|
@ -279,16 +287,20 @@ ifdescribe(process.platform === 'darwin' && !(process.env.CI && process.arch ===
|
|||
nextVersion: string;
|
||||
startFixture: string;
|
||||
endFixture: string;
|
||||
mutateAppPostSign?: {
|
||||
mutate: (appPath: string) => Promise<void>,
|
||||
mutationKey: string,
|
||||
}
|
||||
mutateAppPreSign?: Mutation;
|
||||
mutateAppPostSign?: Mutation;
|
||||
}, fn: (appPath: string, zipPath: string) => Promise<void>) => {
|
||||
await withTempDirectory(async (dir) => {
|
||||
const appPath = await copyApp(dir, opts.startFixture);
|
||||
await opts.mutateAppPreSign?.mutate(appPath);
|
||||
const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
|
||||
await fs.writeFile(
|
||||
infoPath,
|
||||
(await fs.readFile(infoPath, 'utf8')).replace(/(<key>CFBundleShortVersionString<\/key>\s+<string>)[^<]+/g, '$11.0.0')
|
||||
);
|
||||
await signApp(appPath);
|
||||
|
||||
const updateZipPath = await getOrCreateUpdateZipPath(opts.nextVersion, opts.endFixture, opts.mutateAppPostSign);
|
||||
const updateZipPath = await getOrCreateUpdateZipPath(opts.nextVersion, opts.endFixture, opts.mutateAppPreSign, opts.mutateAppPostSign);
|
||||
|
||||
await fn(appPath, updateZipPath);
|
||||
});
|
||||
|
@ -335,6 +347,231 @@ ifdescribe(process.platform === 'darwin' && !(process.env.CI && process.arch ===
|
|||
});
|
||||
});
|
||||
|
||||
it('should hit the download endpoint when an update is available and update successfully when the zip is provided even after a different update was staged', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '2.0.0',
|
||||
startFixture: 'update-stack',
|
||||
endFixture: 'update-stack'
|
||||
}, async (appPath, updateZipPath2) => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '3.0.0',
|
||||
startFixture: 'update-stack',
|
||||
endFixture: 'update-stack'
|
||||
}, async (_, updateZipPath3) => {
|
||||
let updateCount = 0;
|
||||
server.get('/update-file', (req, res) => {
|
||||
res.download(updateCount > 1 ? updateZipPath3 : updateZipPath2);
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
updateCount++;
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const relaunchPromise = new Promise<void>((resolve) => {
|
||||
server.get('/update-check/updated/:version', (req, res) => {
|
||||
res.status(204).send();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 0);
|
||||
expect(launchResult.out).to.include('Update Downloaded');
|
||||
expect(requests).to.have.lengthOf(4);
|
||||
expect(requests[0]).to.have.property('url', '/update-check');
|
||||
expect(requests[1]).to.have.property('url', '/update-file');
|
||||
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||
expect(requests[2]).to.have.property('url', '/update-check');
|
||||
expect(requests[3]).to.have.property('url', '/update-file');
|
||||
expect(requests[2].header('user-agent')).to.include('Electron/');
|
||||
expect(requests[3].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
|
||||
await relaunchPromise;
|
||||
expect(requests).to.have.lengthOf(5);
|
||||
expect(requests[4].url).to.equal('/update-check/updated/3.0.0');
|
||||
expect(requests[4].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update to lower version numbers', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '0.0.1',
|
||||
startFixture: 'update',
|
||||
endFixture: 'update'
|
||||
}, async (appPath, updateZipPath) => {
|
||||
server.get('/update-file', (req, res) => {
|
||||
res.download(updateZipPath);
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const relaunchPromise = new Promise<void>((resolve) => {
|
||||
server.get('/update-check/updated/:version', (req, res) => {
|
||||
res.status(204).send();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 0);
|
||||
expect(launchResult.out).to.include('Update Downloaded');
|
||||
expect(requests).to.have.lengthOf(2);
|
||||
expect(requests[0]).to.have.property('url', '/update-check');
|
||||
expect(requests[1]).to.have.property('url', '/update-file');
|
||||
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
|
||||
await relaunchPromise;
|
||||
expect(requests).to.have.lengthOf(3);
|
||||
expect(requests[2].url).to.equal('/update-check/updated/0.0.1');
|
||||
expect(requests[2].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with ElectronSquirrelPreventDowngrades enabled', () => {
|
||||
it('should not update to lower version numbers', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '0.0.1',
|
||||
startFixture: 'update',
|
||||
endFixture: 'update',
|
||||
mutateAppPreSign: {
|
||||
mutationKey: 'prevent-downgrades',
|
||||
mutate: async (appPath) => {
|
||||
const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
|
||||
await fs.writeFile(
|
||||
infoPath,
|
||||
(await fs.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
|
||||
);
|
||||
}
|
||||
}
|
||||
}, async (appPath, updateZipPath) => {
|
||||
server.get('/update-file', (req, res) => {
|
||||
res.download(updateZipPath);
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 1);
|
||||
expect(launchResult.out).to.include('Cannot update to a bundle with a lower version number');
|
||||
expect(requests).to.have.lengthOf(2);
|
||||
expect(requests[0]).to.have.property('url', '/update-check');
|
||||
expect(requests[1]).to.have.property('url', '/update-file');
|
||||
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should not update to version strings that are not simple Major.Minor.Patch', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '2.0.0-bad',
|
||||
startFixture: 'update',
|
||||
endFixture: 'update',
|
||||
mutateAppPreSign: {
|
||||
mutationKey: 'prevent-downgrades',
|
||||
mutate: async (appPath) => {
|
||||
const infoPath = path.resolve(appPath, 'Contents', 'Info.plist');
|
||||
await fs.writeFile(
|
||||
infoPath,
|
||||
(await fs.readFile(infoPath, 'utf8')).replace('<key>NSSupportsAutomaticGraphicsSwitching</key>', '<key>ElectronSquirrelPreventDowngrades</key><true/><key>NSSupportsAutomaticGraphicsSwitching</key>')
|
||||
);
|
||||
}
|
||||
}
|
||||
}, async (appPath, updateZipPath) => {
|
||||
server.get('/update-file', (req, res) => {
|
||||
res.download(updateZipPath);
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 1);
|
||||
expect(launchResult.out).to.include('Cannot update to a bundle with a lower version number');
|
||||
expect(requests).to.have.lengthOf(2);
|
||||
expect(requests[0]).to.have.property('url', '/update-check');
|
||||
expect(requests[1]).to.have.property('url', '/update-file');
|
||||
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should still update to higher version numbers', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '1.0.1',
|
||||
startFixture: 'update',
|
||||
endFixture: 'update'
|
||||
}, async (appPath, updateZipPath) => {
|
||||
server.get('/update-file', (req, res) => {
|
||||
res.download(updateZipPath);
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const relaunchPromise = new Promise<void>((resolve) => {
|
||||
server.get('/update-check/updated/:version', (req, res) => {
|
||||
res.status(204).send();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 0);
|
||||
expect(launchResult.out).to.include('Update Downloaded');
|
||||
expect(requests).to.have.lengthOf(2);
|
||||
expect(requests[0]).to.have.property('url', '/update-check');
|
||||
expect(requests[1]).to.have.property('url', '/update-file');
|
||||
expect(requests[0].header('user-agent')).to.include('Electron/');
|
||||
expect(requests[1].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
|
||||
await relaunchPromise;
|
||||
expect(requests).to.have.lengthOf(3);
|
||||
expect(requests[2].url).to.equal('/update-check/updated/1.0.1');
|
||||
expect(requests[2].header('user-agent')).to.include('Electron/');
|
||||
});
|
||||
});
|
||||
|
||||
it('should compare version numbers correctly', () => {
|
||||
expect(autoUpdater.isVersionAllowedForUpdate('1.0.0', '2.0.0')).to.equal(true);
|
||||
expect(autoUpdater.isVersionAllowedForUpdate('1.0.1', '1.0.10')).to.equal(true);
|
||||
expect(autoUpdater.isVersionAllowedForUpdate('1.0.10', '1.0.1')).to.equal(false);
|
||||
expect(autoUpdater.isVersionAllowedForUpdate('1.31.1', '1.32.0')).to.equal(true);
|
||||
expect(autoUpdater.isVersionAllowedForUpdate('1.31.1', '0.32.0')).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should abort the update if the application is still running when ShipIt kicks off', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '2.0.0',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue