Migrate app to TypeScript (#1637)

* Initial setup and migrated src/common

* WIP

* WIP

* WIP

* Main module basically finished

* Renderer process migrated

* Added CI step and some fixes

* Fixed remainder of issues and added proper ESLint config

* Fixed a couple issues

* Progress!

* Some more fixes

* Fixed a test

* Fix build step

* PR feedback
This commit is contained in:
Devin Binnie
2021-06-28 09:51:23 -04:00
committed by GitHub
parent 422673a740
commit 1b3d0eac8f
115 changed files with 16246 additions and 9921 deletions

View File

@@ -6,6 +6,8 @@ import {EventEmitter} from 'events';
import log from 'electron-log';
import WindowsRegistry from 'winreg-utf8';
import {RegistryConfig as RegistryConfigType, Team} from 'types/config';
const REGISTRY_HIVE_LIST = [WindowsRegistry.HKLM, WindowsRegistry.HKCU];
const BASE_REGISTRY_KEY_PATH = '\\Software\\Policies\\Mattermost';
export const REGISTRY_READ_EVENT = 'registry-read';
@@ -14,6 +16,9 @@ export const REGISTRY_READ_EVENT = 'registry-read';
* Handles loading config data from the Windows registry set manually or by GPO
*/
export default class RegistryConfig extends EventEmitter {
initialized: boolean;
data: Partial<RegistryConfigType>;
constructor() {
super();
this.initialized = false;
@@ -33,7 +38,7 @@ export default class RegistryConfig extends EventEmitter {
try {
const servers = await this.getServersListFromRegistry();
if (servers.length) {
this.data.teams.push(...servers);
this.data.teams!.push(...servers);
}
} catch (error) {
log.warn('[RegistryConfig] Nothing retrieved for \'DefaultServerList\'', error);
@@ -70,12 +75,12 @@ export default class RegistryConfig extends EventEmitter {
*/
async getServersListFromRegistry() {
const defaultServers = await this.getRegistryEntry(`${BASE_REGISTRY_KEY_PATH}\\DefaultServerList`);
return defaultServers.flat(2).reduce((servers, server, index) => {
return defaultServers.flat(2).reduce((servers: Team[], server, index) => {
if (server) {
servers.push({
name: server.name,
url: server.value,
order: server.order || index,
name: (server as WindowsRegistry.RegistryItem).name,
url: (server as WindowsRegistry.RegistryItem).value,
order: index,
});
}
return servers;
@@ -106,7 +111,7 @@ export default class RegistryConfig extends EventEmitter {
* @param {string} key Path to the registry key to return
* @param {string} name Name of specific entry in the registry key to retrieve (optional)
*/
async getRegistryEntry(key, name) {
async getRegistryEntry(key: string, name?: string) {
const results = [];
for (const hive of REGISTRY_HIVE_LIST) {
results.push(this.getRegistryEntryValues(hive, key, name));
@@ -121,18 +126,18 @@ export default class RegistryConfig extends EventEmitter {
* @param {WindowsRegistry} regKey A configured instance of the WindowsRegistry class
* @param {string} name Name of the specific entry to retrieve (optional)
*/
getRegistryEntryValues(hive, key, name) {
getRegistryEntryValues(hive: string, key: string, name?: string) {
const registry = new WindowsRegistry({hive, key, utf8: true});
return new Promise((resolve, reject) => {
return new Promise<string | WindowsRegistry.RegistryItem[] | undefined>((resolve, reject) => {
try {
registry.values((error, results) => {
registry.values((error: Error, results: WindowsRegistry.RegistryItem[]) => {
if (error || !results || results.length === 0) {
resolve();
resolve(undefined);
return;
}
if (name) { // looking for a single entry value
const registryItem = results.find((item) => item.name === name);
resolve(registryItem && registryItem.value ? registryItem.value : null);
resolve(registryItem && registryItem.value ? registryItem.value : undefined);
} else { // looking for an entry list
resolve(results);
}

View File

@@ -2,6 +2,8 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {BuildConfig} from 'types/config';
// For detailed guides, please refer to https://docs.mattermost.com/deployment/desktop-app-deployment.html
/**
@@ -17,7 +19,7 @@
* when "enableServerManagement is set to false
* @prop {[]} managedResources - Defines which paths are managed
*/
const buildConfig = {
const buildConfig: BuildConfig = {
defaultTeams: [/*
{
name: 'example',

View File

@@ -7,7 +7,9 @@
* @param {number} version - Scheme version. (Not application version)
*/
const getDefaultDownloadLocation = () => {
import {ConfigV2} from 'types/config';
export const getDefaultDownloadLocation = () => {
switch (process.platform) {
case 'darwin':
return `/Users/${process.env.USER || process.env.USERNAME}/Downloads`;
@@ -18,7 +20,7 @@ const getDefaultDownloadLocation = () => {
}
};
const defaultPreferences = {
const defaultPreferences: ConfigV2 = {
version: 2,
teams: [],
showTrayIcon: true,

View File

@@ -9,11 +9,21 @@ import {EventEmitter} from 'events';
import {ipcMain, nativeTheme, app} from 'electron';
import log from 'electron-log';
import * as Validator from '../../main/Validator';
import {
AnyConfig,
BuildConfig,
CombinedConfig,
Config as ConfigType,
LocalConfiguration,
RegistryConfig as RegistryConfigType,
Team,
} from 'types/config';
import {UPDATE_TEAMS, GET_CONFIGURATION, UPDATE_CONFIGURATION, GET_LOCAL_CONFIGURATION} from 'common/communication';
import defaultPreferences from './defaultPreferences';
import * as Validator from '../../main/Validator';
import defaultPreferences, {getDefaultDownloadLocation} from './defaultPreferences';
import upgradeConfigData from './upgradePreferences';
import buildConfig from './buildConfig';
import RegistryConfig, {REGISTRY_READ_EVENT} from './RegistryConfig';
@@ -21,14 +31,25 @@ import RegistryConfig, {REGISTRY_READ_EVENT} from './RegistryConfig';
/**
* Handles loading and merging all sources of configuration as well as saving user provided config
*/
export default class Config extends EventEmitter {
constructor(configFilePath) {
configFilePath: string;
registryConfig?: RegistryConfig;
combinedData?: CombinedConfig;
registryConfigData?: Partial<RegistryConfigType>;
defaultConfigData?: ConfigType;
buildConfigData?: BuildConfig;
localConfigData?: ConfigType;
constructor(configFilePath: string) {
super();
this.configFilePath = configFilePath;
}
// separating constructor from init so main can setup event listeners
init = () => {
init = (): void => {
this.registryConfig = new RegistryConfig();
this.registryConfig.once(REGISTRY_READ_EVENT, this.loadRegistry);
this.registryConfig.init();
@@ -40,7 +61,7 @@ export default class Config extends EventEmitter {
* @param {object} registryData Team configuration from the registry and if teams can be managed by user
*/
loadRegistry = (registryData) => {
loadRegistry = (registryData: Partial<RegistryConfigType>): void => {
this.registryConfigData = registryData;
this.reload();
ipcMain.handle(GET_CONFIGURATION, this.handleGetConfiguration);
@@ -59,11 +80,11 @@ export default class Config extends EventEmitter {
* @emits {update} emitted once all data has been loaded and merged
* @emits {synchronize} emitted when requested by a call to method; used to notify other config instances of changes
*/
reload = () => {
reload = (): void => {
this.defaultConfigData = this.loadDefaultConfigData();
this.buildConfigData = this.loadBuildConfigData();
this.localConfigData = this.loadLocalConfigFile();
this.localConfigData = this.checkForConfigUpdates(this.localConfigData);
const loadedConfig = this.loadLocalConfigFile();
this.localConfigData = this.checkForConfigUpdates(loadedConfig);
this.regenerateCombinedConfigData();
this.emit('update', this.combinedData);
@@ -76,9 +97,9 @@ export default class Config extends EventEmitter {
* @param {string} key name of config property to be saved
* @param {*} data value to save for provided key
*/
set = (key, data) => {
if (key) {
this.localConfigData[key] = data;
set = (key: keyof ConfigType, data: ConfigType[keyof ConfigType]): void => {
if (key && this.localConfigData) {
this.localConfigData = Object.assign({}, this.localConfigData, {[key]: data});
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
@@ -89,13 +110,9 @@ export default class Config extends EventEmitter {
*
* @param {array} properties an array of config properties to save
*/
setMultiple = (event, properties = []) => {
setMultiple = (event: Electron.IpcMainEvent, properties: Array<{key: keyof ConfigType; data: ConfigType[keyof ConfigType]}> = []): Partial<ConfigType> | undefined => {
if (properties.length) {
properties.forEach(({key, data}) => {
if (key) {
this.localConfigData[key] = data;
}
});
this.localConfigData = Object.assign({}, this.localConfigData, ...properties.map(({key, data}) => ({[key]: data})));
this.regenerateCombinedConfigData();
this.saveLocalConfigData();
}
@@ -103,7 +120,7 @@ export default class Config extends EventEmitter {
return this.localConfigData; //this is the only part that changes
}
setRegistryConfigData = (registryConfigData = {teams: []}) => {
setRegistryConfigData = (registryConfigData = {teams: []}): void => {
this.registryConfigData = Object.assign({}, registryConfigData);
this.reload();
}
@@ -113,7 +130,7 @@ export default class Config extends EventEmitter {
*
* @param {object} configData a new, config data object to completely replace the existing config data
*/
replace = (configData) => {
replace = (configData: ConfigType) => {
const newConfigData = configData;
this.localConfigData = Object.assign({}, this.localConfigData, newConfigData);
@@ -129,11 +146,15 @@ export default class Config extends EventEmitter {
* @emits {synchronize} emitted once all data has been saved; used to notify other config instances of changes
* @emits {error} emitted if saving local config data to file fails
*/
saveLocalConfigData = () => {
saveLocalConfigData = (): void => {
if (!this.localConfigData) {
return;
}
try {
this.writeFile(this.configFilePath, this.localConfigData, (error) => {
this.writeFile(this.configFilePath, this.localConfigData, (error: NodeJS.ErrnoException | null) => {
if (error) {
throw new Error(error);
throw new Error(error.message);
}
this.emit('update', this.combinedData);
this.emit('synchronize');
@@ -149,13 +170,13 @@ export default class Config extends EventEmitter {
return this.combinedData;
}
get localData() {
return this.localConfigData;
return this.localConfigData ?? defaultPreferences;
}
get defaultData() {
return this.defaultConfigData;
return this.defaultConfigData ?? defaultPreferences;
}
get buildData() {
return this.buildConfigData;
return this.buildConfigData ?? buildConfig;
}
get registryData() {
return this.registryConfigData;
@@ -164,52 +185,55 @@ export default class Config extends EventEmitter {
// convenience getters
get version() {
return this.combinedData.version;
return this.combinedData?.version ?? defaultPreferences.version;
}
get teams() {
return this.combinedData.teams;
return this.combinedData?.teams ?? defaultPreferences.teams;
}
get darkMode() {
return this.combinedData.darkMode;
return this.combinedData?.darkMode ?? defaultPreferences.darkMode;
}
get localTeams() {
return this.localConfigData.teams;
return this.localConfigData?.teams ?? defaultPreferences.version;
}
get predefinedTeams() {
return [...this.buildConfigData.defaultTeams, ...this.registryConfigData.teams];
return [...this.buildConfigData?.defaultTeams ?? [], ...this.registryConfigData?.teams ?? []];
}
get enableHardwareAcceleration() {
return this.combinedData.enableHardwareAcceleration;
return this.combinedData?.enableHardwareAcceleration ?? defaultPreferences.enableHardwareAcceleration;
}
get enableServerManagement() {
return this.combinedData.enableServerManagement;
return this.combinedData?.enableServerManagement ?? buildConfig.enableServerManagement;
}
get enableAutoUpdater() {
return this.combinedData.enableAutoUpdater;
return this.combinedData?.enableAutoUpdater ?? buildConfig.enableAutoUpdater;
}
get autostart() {
return this.combinedData.autostart;
return this.combinedData?.autostart ?? defaultPreferences.autostart;
}
get notifications() {
return this.combinedData.notifications;
return this.combinedData?.notifications ?? defaultPreferences.notifications;
}
get showUnreadBadge() {
return this.combinedData.showUnreadBadge;
return this.combinedData?.showUnreadBadge ?? defaultPreferences.showUnreadBadge;
}
get useSpellChecker() {
return this.combinedData.useSpellChecker;
return this.combinedData?.useSpellChecker ?? defaultPreferences.useSpellChecker;
}
get spellCheckerLocale() {
return this.combinedData.spellCheckerLocale;
return this.combinedData?.spellCheckerLocale ?? defaultPreferences.spellCheckerLocale;
}
get showTrayIcon() {
return this.combinedData.showTrayIcon;
return this.combinedData?.showTrayIcon ?? defaultPreferences.showTrayIcon;
}
get trayIconTheme() {
return this.combinedData.trayIconTheme;
return this.combinedData?.trayIconTheme ?? defaultPreferences.trayIconTheme;
}
get downloadLocation() {
return this.combinedData?.downloadLocation ?? getDefaultDownloadLocation();
}
get helpLink() {
return this.combinedData.helpLink;
return this.combinedData?.helpLink;
}
// initialization/processing methods
@@ -231,22 +255,21 @@ export default class Config extends EventEmitter {
/**
* Loads and returns locally stored config data from the filesystem or returns app defaults if no file is found
*/
loadLocalConfigFile = () => {
let configData = {};
loadLocalConfigFile = (): AnyConfig => {
let configData: AnyConfig;
try {
configData = this.readFileSync(this.configFilePath);
// validate based on config file version
if (configData.version > 1) {
configData = Validator.validateV2ConfigData(configData);
} else {
switch (configData.version) {
case 1:
configData = Validator.validateV1ConfigData(configData);
break;
default:
configData = Validator.validateV0ConfigData(configData);
}
switch (configData.version) {
case 2:
configData = Validator.validateV2ConfigData(configData)!;
break;
case 1:
configData = Validator.validateV1ConfigData(configData)!;
break;
default:
configData = Validator.validateV0ConfigData(configData)!;
}
if (!configData) {
throw new Error('Provided configuration file does not validate, using defaults instead.');
@@ -255,12 +278,6 @@ export default class Config extends EventEmitter {
log.warn('Failed to load configuration file from the filesystem. Using defaults.');
configData = this.copy(this.defaultConfigData);
// add default team to teams if one exists and there arent currently any teams
if (!configData.teams.length && this.defaultConfigData.defaultTeam) {
configData.teams.push(this.defaultConfigData.defaultTeam);
}
delete configData.defaultTeam;
this.writeFileSync(this.configFilePath, configData);
}
return configData;
@@ -271,18 +288,21 @@ export default class Config extends EventEmitter {
*
* @param {*} data locally stored data
*/
checkForConfigUpdates = (data) => {
checkForConfigUpdates = (data: AnyConfig) => {
let configData = data;
try {
if (configData.version !== this.defaultConfigData.version) {
configData = upgradeConfigData(configData);
this.writeFileSync(this.configFilePath, configData);
log.info(`Configuration updated to version ${this.defaultConfigData.version} successfully.`);
if (this.defaultConfigData) {
try {
if (configData.version !== this.defaultConfigData.version) {
configData = upgradeConfigData(configData);
this.writeFileSync(this.configFilePath, configData);
log.info(`Configuration updated to version ${this.defaultConfigData.version} successfully.`);
}
} catch (error) {
log.error(`Failed to update configuration to version ${this.defaultConfigData.version}.`);
}
} catch (error) {
log.error(`Failed to update configuration to version ${this.defaultConfigData.version}.`);
}
return configData;
return configData as ConfigType;
}
/**
@@ -293,38 +313,37 @@ export default class Config extends EventEmitter {
this.combinedData = Object.assign({}, this.defaultConfigData, this.localConfigData, this.buildConfigData, this.registryConfigData);
// remove unecessary data pulled from default and build config
delete this.combinedData.defaultTeam;
delete this.combinedData.defaultTeams;
delete this.combinedData!.defaultTeams;
// IMPORTANT: properly combine teams from all sources
let combinedTeams = [];
// - start by adding default teams from buildConfig, if any
if (this.buildConfigData.defaultTeams && this.buildConfigData.defaultTeams.length) {
if (this.buildConfigData?.defaultTeams?.length) {
combinedTeams.push(...this.buildConfigData.defaultTeams);
}
// - add registry defined teams, if any
if (this.registryConfigData.teams && this.registryConfigData.teams.length) {
if (this.registryConfigData?.teams?.length) {
combinedTeams.push(...this.registryConfigData.teams);
}
// - add locally defined teams only if server management is enabled
if (this.enableServerManagement) {
combinedTeams.push(...this.localConfigData.teams);
if (this.localConfigData && this.enableServerManagement) {
combinedTeams.push(...this.localConfigData.teams || []);
}
combinedTeams = this.filterOutDuplicateTeams(combinedTeams);
combinedTeams = this.sortUnorderedTeams(combinedTeams);
this.combinedData.teams = combinedTeams;
this.combinedData.localTeams = this.localConfigData.teams;
this.combinedData.buildTeams = this.buildConfigData.defaultTeams;
this.combinedData.registryTeams = this.registryConfigData.teams;
if (process.platform === 'darwin' || process.platform === 'win32') {
this.combinedData.darkMode = nativeTheme.shouldUseDarkColors;
if (this.combinedData) {
this.combinedData.teams = combinedTeams;
this.combinedData.registryTeams = this.registryConfigData?.teams || [];
if (process.platform === 'darwin' || process.platform === 'win32') {
this.combinedData.darkMode = nativeTheme.shouldUseDarkColors;
}
this.combinedData.appName = app.name;
}
this.combinedData.appName = app.name;
}
/**
@@ -332,7 +351,7 @@ export default class Config extends EventEmitter {
*
* @param {array} teams array of teams to check for duplicates
*/
filterOutDuplicateTeams = (teams) => {
filterOutDuplicateTeams = (teams: Team[]) => {
let newTeams = teams;
const uniqueURLs = new Set();
newTeams = newTeams.filter((team) => {
@@ -345,7 +364,7 @@ export default class Config extends EventEmitter {
* Returns the provided array fo teams with existing teams filtered out
* @param {array} teams array of teams to check for already defined teams
*/
filterOutPredefinedTeams = (teams) => {
filterOutPredefinedTeams = (teams: Team[]) => {
let newTeams = teams;
// filter out predefined teams
@@ -360,17 +379,17 @@ export default class Config extends EventEmitter {
* Apply a default sort order to the team list, if no order is specified.
* @param {array} teams to sort
*/
sortUnorderedTeams = (teams) => {
sortUnorderedTeams = (teams: Team[]) => {
// We want to preserve the array order of teams in the config, otherwise a lot of bugs will occur
const mappedTeams = teams.map((team, index) => ({team, originalOrder: index}));
// Make a best pass at interpreting sort order. If an order is not specified, assume it is 0.
//
const newTeams = mappedTeams.sort((x, y) => {
if (x.team.order == null) {
if (!x.team.order) {
x.team.order = 0;
}
if (y.team.order == null) {
if (!y.team.order) {
y.team.order = 0;
}
@@ -390,11 +409,15 @@ export default class Config extends EventEmitter {
// helper functions
readFileSync = (filePath) => {
readFileSync = (filePath: string) => {
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
}
writeFile = (filePath, configData, callback) => {
writeFile = (filePath: string, configData: Partial<ConfigType>, callback: fs.NoParamCallback) => {
if (!this.defaultConfigData) {
return;
}
if (configData.version !== this.defaultConfigData.version) {
throw new Error('version ' + configData.version + ' is not equal to ' + this.defaultConfigData.version);
}
@@ -402,7 +425,11 @@ export default class Config extends EventEmitter {
fs.writeFile(filePath, json, 'utf8', callback);
}
writeFileSync = (filePath, config) => {
writeFileSync = (filePath: string, config: Partial<ConfigType>) => {
if (!this.defaultConfigData) {
return;
}
if (config.version !== this.defaultConfigData.version) {
throw new Error('version ' + config.version + ' is not equal to ' + this.defaultConfigData.version);
}
@@ -416,15 +443,15 @@ export default class Config extends EventEmitter {
fs.writeFileSync(filePath, json, 'utf8');
}
merge = (base, target) => {
merge = <T, T2>(base: T, target: T2) => {
return Object.assign({}, base, target);
}
copy = (data) => {
copy = <T>(data: T) => {
return Object.assign({}, data);
}
handleGetConfiguration = (event, option) => {
handleGetConfiguration = (event: Electron.IpcMainInvokeEvent, option: keyof CombinedConfig) => {
const config = {...this.combinedData};
if (option) {
return config[option];
@@ -432,19 +459,19 @@ export default class Config extends EventEmitter {
return config;
}
handleGetLocalConfiguration = (event, option) => {
const config = {...this.localConfigData};
handleGetLocalConfiguration = (event: Electron.IpcMainInvokeEvent, option: keyof ConfigType) => {
const config: Partial<LocalConfiguration> = {...this.localConfigData};
config.appName = app.name;
config.enableServerManagement = this.combinedData.enableServerManagement;
config.enableServerManagement = this.combinedData?.enableServerManagement;
if (option) {
return config[option];
}
return config;
}
handleUpdateTeams = (event, newTeams) => {
handleUpdateTeams = (event: Electron.IpcMainInvokeEvent, newTeams: Team[]) => {
this.set('teams', newTeams);
return this.combinedData.teams;
return this.combinedData!.teams;
}
/**
@@ -452,7 +479,7 @@ export default class Config extends EventEmitter {
* @emits 'darkModeChange'
*/
handleUpdateTheme = () => {
if (this.combinedData.darkMode !== nativeTheme.shouldUseDarkColors) {
if (this.combinedData && this.combinedData.darkMode !== nativeTheme.shouldUseDarkColors) {
this.combinedData.darkMode = nativeTheme.shouldUseDarkColors;
this.emit('darkModeChange', this.combinedData.darkMode);
}
@@ -463,6 +490,10 @@ export default class Config extends EventEmitter {
* @emits 'darkModeChange'
*/
toggleDarkModeManually = () => {
if (!this.combinedData) {
return;
}
this.set('darkMode', !this.combinedData.darkMode);
this.emit('darkModeChange', this.combinedData.darkMode);
}

View File

@@ -1,12 +1,14 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ConfigV0, ConfigV1} from 'types/config';
import defaultPreferences from './defaultPreferences';
const pastDefaultPreferences = {
0: {
url: '',
},
} as ConfigV0,
1: {
version: 1,
teams: [],
@@ -23,9 +25,8 @@ const pastDefaultPreferences = {
enableHardwareAcceleration: true,
autostart: true,
spellCheckerLocale: 'en-US',
},
} as ConfigV1,
2: defaultPreferences,
};
pastDefaultPreferences[`${defaultPreferences.version}`] = defaultPreferences;
export default pastDefaultPreferences;

View File

@@ -1,42 +0,0 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import pastDefaultPreferences from './pastDefaultPreferences';
function deepCopy(object) {
return JSON.parse(JSON.stringify(object));
}
function upgradeV0toV1(configV0) {
const config = deepCopy(pastDefaultPreferences['1']);
if (config.version !== 1) {
throw new Error('pastDefaultPreferences[\'1\'].version is not equal to 1');
}
config.teams.push({
name: 'Primary team',
url: configV0.url,
});
return config;
}
function upgradeV1toV2(configV1) {
const config = deepCopy(configV1);
config.version = 2;
config.teams.forEach((value, index) => {
value.order = index;
});
config.darkMode = false;
return config;
}
export default function upgradeToLatest(config) {
const configVersion = config.version ? config.version : 0;
switch (configVersion) {
case 1:
return upgradeToLatest(upgradeV1toV2(config));
case 0:
return upgradeToLatest(upgradeV0toV1(config));
default:
return config;
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) 2015-2016 Yuya Ochiai
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ConfigV2, ConfigV1, ConfigV0, AnyConfig} from 'types/config';
import pastDefaultPreferences from './pastDefaultPreferences';
function deepCopy<T>(object: T): T {
return JSON.parse(JSON.stringify(object));
}
function upgradeV0toV1(configV0: ConfigV0) {
const config = deepCopy(pastDefaultPreferences[1]);
config.teams.push({
name: 'Primary team',
url: configV0.url,
});
return config;
}
function upgradeV1toV2(configV1: ConfigV1) {
const config: ConfigV2 = Object.assign({}, deepCopy<ConfigV2>(pastDefaultPreferences[2]), configV1);
config.version = 2;
config.teams = configV1.teams.map((value, index) => {
return {
...value,
order: index,
};
});
return config;
}
export default function upgradeToLatest(config: AnyConfig): ConfigV2 {
switch (config.version) {
case 2:
return config as ConfigV2;
case 1:
return upgradeToLatest(upgradeV1toV2(config as ConfigV1));
default:
return upgradeToLatest(upgradeV0toV1(config as ConfigV0));
}
}