[MM-42538] Submit nightly builds to TestFlight for macOS (#2023)

* Initial MAS build, working on TestFlight

* Migration of old configs to MAS

* Ignore fastlane files

* Add mac app store build to nightly build

* Revert Me - For testing in PR

* Don't need to install fastlane

* BIG D

* Fix patch updater script to allow for no yml

* Nevermind, do this instead

* Update xcode

* Let's try a fake version that works

* Revert version and rename for test flight

* Use Xcode 13.0.0

* Use CircleCI build number when available

* Revert testing changes

* Remove notarize for MAS

* Change vars to MACOS instead of IOS

* Revert electron-builder to v22

* Revert package-lock.json

Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
This commit is contained in:
Devin Binnie
2022-03-28 11:06:00 -04:00
committed by GitHub
parent bc7f82fbf3
commit 0a7be91576
16 changed files with 359 additions and 12 deletions

Binary file not shown.

View File

@@ -91,6 +91,7 @@ import {
updateSpellCheckerLocales,
wasUpdated,
initCookieManager,
migrateMacAppStore,
} from './utils';
export const mainProtocol = protocols?.[0]?.schemes?.[0];
@@ -118,6 +119,13 @@ export async function initialize() {
return;
}
// eslint-disable-next-line no-undef
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (__IS_MAC_APP_STORE__) {
migrateMacAppStore();
}
// initialization that should run once the app is ready
initializeInterCommunicationEventListeners();
initializeAfterAppReady();
@@ -198,10 +206,15 @@ function initializeBeforeAppReady() {
refreshTrayImages(Config.trayIconTheme);
// If there is already an instance, quit this one
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.exit();
global.willAppQuit = true;
// eslint-disable-next-line no-undef
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
if (!__IS_MAC_APP_STORE__) {
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.exit();
global.willAppQuit = true;
}
}
AllowProtocolDialog.init();

View File

@@ -1,27 +1,58 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import fs from 'fs';
import {dialog} from 'electron';
import Config from 'common/config';
import JsonFileManager from 'common/JsonFileManager';
import {TAB_MESSAGING, TAB_FOCALBOARD, TAB_PLAYBOOKS} from 'common/tabs/TabView';
import Utils from 'common/utils/util';
import {updatePaths} from 'main/constants';
import {ServerInfo} from 'main/server/serverInfo';
import {getDeeplinkingURL, updateServerInfos, resizeScreen} from './utils';
import {getDeeplinkingURL, updateServerInfos, resizeScreen, migrateMacAppStore} from './utils';
jest.mock('fs', () => ({
readFileSync: jest.fn(),
writeFileSync: jest.fn(),
existsSync: jest.fn(),
}));
jest.mock('electron', () => ({
app: {
getPath: () => '/path/to/data',
getAppPath: () => '/path/to/app',
},
nativeImage: {
createFromPath: jest.fn(),
},
dialog: {
showOpenDialogSync: jest.fn(),
showMessageBoxSync: jest.fn(),
},
}));
jest.mock('electron-log', () => ({
info: jest.fn(),
error: jest.fn(),
}));
jest.mock('common/config', () => ({
set: jest.fn(),
}));
jest.mock('common/JsonFileManager');
jest.mock('common/utils/util', () => ({
isVersionGreaterThanOrEqualTo: jest.fn(),
getDisplayBoundaries: jest.fn(),
}));
jest.mock('main/autoUpdater', () => ({}));
jest.mock('main/constants', () => ({
updatePaths: jest.fn(),
}));
jest.mock('main/menus/app', () => ({}));
jest.mock('main/menus/tray', () => ({}));
jest.mock('main/server/serverInfo', () => ({
@@ -190,4 +221,73 @@ describe('main/app/utils', () => {
expect(browserWindow.center).toHaveBeenCalled();
});
});
describe('migrateMacAppStore', () => {
it('should skip migration if already migrated', () => {
JsonFileManager.mockImplementation(() => ({
getValue: () => true,
}));
migrateMacAppStore();
expect(dialog.showMessageBoxSync).not.toHaveBeenCalled();
});
it('should skip migration if folder does not exist', () => {
JsonFileManager.mockImplementation(() => ({
getValue: () => false,
}));
fs.existsSync.mockReturnValue(false);
migrateMacAppStore();
expect(fs.existsSync).toHaveBeenCalled();
expect(dialog.showMessageBoxSync).not.toHaveBeenCalled();
});
it('should skip migration and set value if the user rejects import', () => {
const migrationPrefs = {
getValue: () => false,
setValue: jest.fn(),
};
JsonFileManager.mockImplementation(() => migrationPrefs);
fs.existsSync.mockReturnValue(true);
dialog.showMessageBoxSync.mockReturnValue(1);
migrateMacAppStore();
expect(migrationPrefs.setValue).toHaveBeenCalledWith('masConfigs', true);
expect(dialog.showOpenDialogSync).not.toHaveBeenCalled();
});
it('should do nothing if no directory is chosen, or if the dialog is closed', () => {
JsonFileManager.mockImplementation(() => ({
getValue: () => false,
}));
fs.existsSync.mockReturnValue(true);
dialog.showMessageBoxSync.mockReturnValue(0);
dialog.showOpenDialogSync.mockReturnValue([]);
migrateMacAppStore();
expect(dialog.showOpenDialogSync).toHaveBeenCalled();
expect(updatePaths).not.toHaveBeenCalled();
});
it('should copy all of the configs when they exist to the new directory', () => {
const migrationPrefs = {
getValue: () => false,
setValue: jest.fn(),
};
JsonFileManager.mockImplementation(() => migrationPrefs);
fs.readFileSync.mockReturnValue('config-data');
fs.existsSync.mockImplementation((path) => {
if (path === '/Library/Application Support/Mattermost') {
return true;
}
return ['config', 'app-state', 'bounds-info', 'migration-info'].some((filename) => path.endsWith(`${filename}.json`));
});
dialog.showMessageBoxSync.mockReturnValue(0);
dialog.showOpenDialogSync.mockReturnValue(['/old/data/path']);
migrateMacAppStore();
expect(fs.readFileSync).toHaveBeenCalledWith('/old/data/path/config.json');
expect(fs.writeFileSync).toHaveBeenCalledWith('/path/to/data/config.json', 'config-data');
expect(fs.readFileSync).not.toHaveBeenCalledWith('/old/data/path/allowedProtocols.json');
expect(fs.writeFileSync).not.toHaveBeenCalledWith('/path/to/data/allowedProtocols.json', 'config-data');
expect(updatePaths).toHaveBeenCalled();
expect(migrationPrefs.setValue).toHaveBeenCalledWith('masConfigs', true);
});
});
});

View File

@@ -1,20 +1,26 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {app, BrowserWindow, Menu, Rectangle, Session, session} from 'electron';
import fs from 'fs';
import path from 'path';
import {app, BrowserWindow, Menu, Rectangle, Session, session, dialog, nativeImage} from 'electron';
import log from 'electron-log';
import {TeamWithTabs} from 'types/config';
import {MigrationInfo, TeamWithTabs} from 'types/config';
import {RemoteInfo} from 'types/server';
import {Boundaries} from 'types/utils';
import Config from 'common/config';
import JsonFileManager from 'common/JsonFileManager';
import {MattermostServer} from 'common/servers/MattermostServer';
import {TAB_FOCALBOARD, TAB_MESSAGING, TAB_PLAYBOOKS} from 'common/tabs/TabView';
import urlUtils from 'common/utils/url';
import Utils from 'common/utils/util';
import updateManager from 'main/autoUpdater';
import {migrationInfoPath, updatePaths} from 'main/constants';
import {createMenu as createAppMenu} from 'main/menus/app';
import {createMenu as createTrayMenu} from 'main/menus/tray';
import {ServerInfo} from 'main/server/serverInfo';
@@ -23,6 +29,12 @@ import WindowManager from 'main/windows/windowManager';
import {mainProtocol} from './initialize';
const configFileNames = ['config', 'allowedProtocols', 'app-state', 'certificate', 'trustedOrigins', 'bounds-info', 'migration-info'];
const assetsDir = path.resolve(app.getAppPath(), 'assets');
const appIconURL = path.resolve(assetsDir, 'appicon_with_spacing_32.png');
const appIcon = nativeImage.createFromPath(appIconURL);
export function openDeepLink(deeplinkingUrl: string) {
try {
WindowManager.showMainWindow(deeplinkingUrl);
@@ -177,3 +189,62 @@ export function initCookieManager(session: Session) {
flushCookiesStore(session);
});
}
export function migrateMacAppStore() {
const migrationPrefs = new JsonFileManager<MigrationInfo>(migrationInfoPath);
const oldPath = path.join(app.getPath('userData'), '../../../../../../../Library/Application Support/Mattermost');
// Check if we've already migrated
if (migrationPrefs.getValue('masConfigs')) {
return;
}
// Check if the files are there to migrate
try {
const exists = fs.existsSync(oldPath);
if (!exists) {
log.info('MAS: No files to migrate, skipping');
return;
}
} catch (e) {
log.error('MAS: Failed to check for existing Mattermost Desktop install, skipping', e);
return;
}
const cancelImport = dialog.showMessageBoxSync({
title: 'Mattermost',
message: 'Import Existing Configuration',
detail: 'It appears that an existing Mattermost configuration exists, would you like to import it? You will be asked to pick the correct configuration directory.',
icon: appIcon,
buttons: ['Select Directory and Import', 'Don\'t Import'],
type: 'info',
defaultId: 0,
cancelId: 1,
});
if (cancelImport) {
migrationPrefs.setValue('masConfigs', true);
return;
}
const result = dialog.showOpenDialogSync({
defaultPath: oldPath,
properties: ['openDirectory'],
});
if (!(result && result[0])) {
return;
}
try {
for (const fileName of configFileNames) {
if (fs.existsSync(path.resolve(result[0], `${fileName}.json`))) {
const contents = fs.readFileSync(path.resolve(result[0], `${fileName}.json`));
fs.writeFileSync(path.resolve(app.getPath('userData'), `${fileName}.json`), contents);
}
}
updatePaths(true);
migrationPrefs.setValue('masConfigs', true);
} catch (e) {
log.error('MAS: An error occurred importing the existing configuration', e);
}
}

View File

@@ -126,4 +126,5 @@ export type LocalConfiguration = Config & {
export type MigrationInfo = {
updateTrayIconWin32: boolean;
masConfigs: boolean;
}