diff --git a/package-lock.json b/package-lock.json index 33e9dd08104..6b880370f0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "dependencies": { "@material/material-color-utilities": "^0.2.7", + "compare-versions": "^6.1.1", "crypto-js": "^4.2.0", "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", @@ -3605,6 +3606,12 @@ "node": ">=18" } }, + "node_modules/compare-versions": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.1.tgz", + "integrity": "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==", + "license": "MIT" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/package.json b/package.json index 6b1c73db158..c84e926fc35 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ }, "dependencies": { "@material/material-color-utilities": "^0.2.7", + "compare-versions": "^6.1.1", "crypto-js": "^4.2.0", "i18next": "^24.2.2", "i18next-browser-languagedetector": "^8.0.4", diff --git a/src/@types/SessionSaveMigrator.ts b/src/@types/SessionSaveMigrator.ts new file mode 100644 index 00000000000..c4b0ad8dda4 --- /dev/null +++ b/src/@types/SessionSaveMigrator.ts @@ -0,0 +1,6 @@ +import type { SessionSaveData } from "#app/system/game-data"; + +export interface SessionSaveMigrator { + version: string; + migrate: (data: SessionSaveData) => void; +} diff --git a/src/@types/SettingsSaveMigrator.ts b/src/@types/SettingsSaveMigrator.ts new file mode 100644 index 00000000000..aae3df7cc60 --- /dev/null +++ b/src/@types/SettingsSaveMigrator.ts @@ -0,0 +1,5 @@ +export interface SettingsSaveMigrator { + version: string; + // biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings + migrate: (data: Object) => void; +} diff --git a/src/@types/SystemSaveMigrator.ts b/src/@types/SystemSaveMigrator.ts new file mode 100644 index 00000000000..a22b5f6c93d --- /dev/null +++ b/src/@types/SystemSaveMigrator.ts @@ -0,0 +1,6 @@ +import type { SystemSaveData } from "#app/system/game-data"; + +export interface SystemSaveMigrator { + version: string; + migrate: (data: SystemSaveData) => void; +} diff --git a/src/system/version_migration/version_converter.ts b/src/system/version_migration/version_converter.ts index 074f60c2c5d..4b712609819 100644 --- a/src/system/version_migration/version_converter.ts +++ b/src/system/version_migration/version_converter.ts @@ -1,19 +1,104 @@ -import type { SessionSaveData, SystemSaveData } from "../game-data"; +import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; +import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator"; +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; +import type { SessionSaveData, SystemSaveData } from "#app/system/game-data"; +import { compareVersions } from "compare-versions"; import { version } from "../../../package.json"; +/* +// template for save migrator creation +// versions/vA_B_C.ts + +// The version for each migrator should match the filename, ie: `vA_B_C.ts` -> `version: "A.B.C" +// This is the target version (aka the version we're ending up on after the migrators are run) + +// The name for each migrator should match its purpose. For example, if you're fixing +// the ability index of a pokemon, it might be called `migratePokemonAbilityIndex` + +const systemMigratorA: SystemSaveMigrator = { + version: "A.B.C", + migrate: (data: SystemSaveData): void => { + // migration code goes here + }, +}; + +export const systemMigrators: Readonly = [systemMigratorA] as const; + +const sessionMigratorA: SessionSaveMigrator = { + version: "A.B.C", + migrate: (data: SessionSaveData): void => { + // migration code goes here + }, +}; + +export const sessionMigrators: Readonly = [sessionMigratorA] as const; + +const settingsMigratorA: SettingsSaveMigrator = { + version: "A.B.C", + // biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings + migrate: (data: Object): void => { + // migration code goes here + }, +}; + +export const settingsMigrators: Readonly = [settingsMigratorA] as const; +*/ + +// --- vA.B.C PATCHES --- // +// import * as vA_B_C from "./versions/vA_B_C"; + // --- v1.0.4 (and below) PATCHES --- // import * as v1_0_4 from "./versions/v1_0_4"; -// --- v1.1.0 PATCHES --- // -import * as v1_1_0 from "./versions/v1_1_0"; - // --- v1.7.0 PATCHES --- // import * as v1_7_0 from "./versions/v1_7_0"; // --- v1.8.3 PATCHES --- // import * as v1_8_3 from "./versions/v1_8_3"; -const LATEST_VERSION = version.split(".").map(value => Number.parseInt(value)); +/** Current game version */ +const LATEST_VERSION = version; + +type SaveMigrator = SystemSaveMigrator | SessionSaveMigrator | SettingsSaveMigrator; + +// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings +type SaveData = SystemSaveData | SessionSaveData | Object; + +// To add a new set of migrators, create a new `.push()` line like so: +// `systemMigrators.push(...vA_B_C.systemMigrators);` + +/** All system save migrators */ +const systemMigrators: SystemSaveMigrator[] = []; +systemMigrators.push(...v1_0_4.systemMigrators); +systemMigrators.push(...v1_7_0.systemMigrators); +systemMigrators.push(...v1_8_3.systemMigrators); + +/** All session save migrators */ +const sessionMigrators: SessionSaveMigrator[] = []; +sessionMigrators.push(...v1_0_4.sessionMigrators); +sessionMigrators.push(...v1_7_0.sessionMigrators); + +/** All settings migrators */ +const settingsMigrators: SettingsSaveMigrator[] = []; +settingsMigrators.push(...v1_0_4.settingsMigrators); + +/** Sorts migrators by their stated version, ensuring they are applied in order from oldest to newest */ +const sortMigrators = (migrators: SaveMigrator[]): void => { + migrators.sort((a, b) => compareVersions(a.version, b.version)); +}; + +sortMigrators(systemMigrators); +sortMigrators(sessionMigrators); +sortMigrators(settingsMigrators); + +const applyMigrators = (migrators: readonly SaveMigrator[], data: SaveData, saveVersion: string) => { + for (const migrator of migrators) { + const isMigratorVersionHigher = compareVersions(saveVersion, migrator.version) === -1; + if (isMigratorVersionHigher) { + migrator.migrate(data as any); + } + } +}; /** * Converts incoming {@linkcode SystemSaveData} that has a version below the @@ -26,12 +111,12 @@ const LATEST_VERSION = version.split(".").map(value => Number.parseInt(value)); * @see {@link SystemVersionConverter} */ export function applySystemVersionMigration(data: SystemSaveData) { - const curVersion = data.gameVersion.split(".").map(value => Number.parseInt(value)); + const prevVersion = data.gameVersion; + const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1; - if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { - const converter = new SystemVersionConverter(); - converter.applyStaticPreprocessors(data); - converter.applyMigration(data, curVersion); + if (isCurrentVersionHigher) { + applyMigrators(systemMigrators, data, prevVersion); + console.log(`System data successfully migrated to v${LATEST_VERSION}!`); } } @@ -46,12 +131,15 @@ export function applySystemVersionMigration(data: SystemSaveData) { * @see {@link SessionVersionConverter} */ export function applySessionVersionMigration(data: SessionSaveData) { - const curVersion = data.gameVersion.split(".").map(value => Number.parseInt(value)); + const prevVersion = data.gameVersion; + const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1; - if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { - const converter = new SessionVersionConverter(); - converter.applyStaticPreprocessors(data); - converter.applyMigration(data, curVersion); + if (isCurrentVersionHigher) { + // Always sanitize money as a safeguard + data.money = Math.floor(data.money); + + applyMigrators(sessionMigrators, data, prevVersion); + console.log(`Session data successfully migrated to v${LATEST_VERSION}!`); } } @@ -65,156 +153,13 @@ export function applySessionVersionMigration(data: SessionSaveData) { * @param data Settings data object * @see {@link SettingsVersionConverter} */ +// biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings export function applySettingsVersionMigration(data: Object) { - const gameVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0"; - const curVersion = gameVersion.split(".").map(value => Number.parseInt(value)); + const prevVersion: string = data.hasOwnProperty("gameVersion") ? data["gameVersion"] : "1.0.0"; + const isCurrentVersionHigher = compareVersions(prevVersion, LATEST_VERSION) === -1; - if (!curVersion.every((value, index) => value === LATEST_VERSION[index])) { - const converter = new SettingsVersionConverter(); - converter.applyStaticPreprocessors(data); - converter.applyMigration(data, curVersion); - } -} - -/** - * Abstract class encapsulating the logic for migrating data from a given version up to - * the current version listed in `package.json`. - * - * Note that, for any version converter, the corresponding `applyMigration` - * function would only need to be changed once when the first migration for a - * given version is introduced. Similarly, a version file (within the `versions` - * folder) would only need to be created for a version once with the appropriate - * array nomenclature. - */ -abstract class VersionConverter { - /** - * Iterates through an array of designated migration functions that are each - * called one by one to transform the data. - * @param data The data to be operated on - * @param migrationArr An array of functions that will transform the incoming data - */ - callMigrators(data: any, migrationArr: readonly any[]) { - for (const migrate of migrationArr) { - migrate(data); - } - } - - /** - * Applies any version-agnostic data sanitation as defined within the function - * body. - * @param data The data to be operated on - */ - applyStaticPreprocessors(_data: any): void {} - - /** - * Uses the current version the incoming data to determine the starting point - * of the migration which will cascade up to the latest version, calling the - * necessary migration functions in the process. - * @param data The data to be operated on - * @param curVersion [0] Current major version - * [1] Current minor version - * [2] Current patch version - */ - abstract applyMigration(data: any, curVersion: number[]): void; -} - -/** - * Class encapsulating the logic for migrating {@linkcode SessionSaveData} from - * a given version up to the current version listed in `package.json`. - * @extends VersionConverter - */ -class SessionVersionConverter extends VersionConverter { - override applyStaticPreprocessors(data: SessionSaveData): void { - // Always sanitize money as a safeguard - data.money = Math.floor(data.money); - } - - override applyMigration(data: SessionSaveData, curVersion: number[]): void { - const [curMajor, curMinor, curPatch] = curVersion; - - if (curMajor === 1) { - if (curMinor === 0) { - if (curPatch <= 5) { - console.log("Applying v1.0.4 session data migration!"); - this.callMigrators(data, v1_0_4.sessionMigrators); - } - } - if (curMinor <= 1) { - console.log("Applying v1.1.0 session data migration!"); - this.callMigrators(data, v1_1_0.sessionMigrators); - } - if (curMinor < 7) { - console.log("Applying v1.7.0 session data migration!"); - this.callMigrators(data, v1_7_0.sessionMigrators); - } - } - - console.log(`Session data successfully migrated to v${version}!`); - } -} - -/** - * Class encapsulating the logic for migrating {@linkcode SystemSaveData} from - * a given version up to the current version listed in `package.json`. - * @extends VersionConverter - */ -class SystemVersionConverter extends VersionConverter { - override applyMigration(data: SystemSaveData, curVersion: number[]): void { - const [curMajor, curMinor, curPatch] = curVersion; - - if (curMajor === 1) { - if (curMinor === 0) { - if (curPatch <= 4) { - console.log("Applying v1.0.4 system data migraton!"); - this.callMigrators(data, v1_0_4.systemMigrators); - } - } - if (curMinor <= 1) { - console.log("Applying v1.1.0 system data migraton!"); - this.callMigrators(data, v1_1_0.systemMigrators); - } - if (curMinor < 7) { - console.log("Applying v1.7.0 system data migration!"); - this.callMigrators(data, v1_7_0.systemMigrators); - } - if (curMinor === 8) { - if (curPatch <= 2) { - console.log("Applying v1.8.3 system data migration!"); - this.callMigrators(data, v1_8_3.systemMigrators); - } - } - } - - console.log(`System data successfully migrated to v${version}!`); - } -} - -/** - * Class encapsulating the logic for migrating settings data from - * a given version up to the current version listed in `package.json`. - * @extends VersionConverter - */ -class SettingsVersionConverter extends VersionConverter { - override applyMigration(data: Object, curVersion: number[]): void { - const [curMajor, curMinor, curPatch] = curVersion; - - if (curMajor === 1) { - if (curMinor === 0) { - if (curPatch <= 4) { - console.log("Applying v1.0.4 settings data migraton!"); - this.callMigrators(data, v1_0_4.settingsMigrators); - } - } - if (curMinor <= 1) { - console.log("Applying v1.1.0 settings data migraton!"); - this.callMigrators(data, v1_1_0.settingsMigrators); - } - if (curMinor < 7) { - console.log("Applying v1.7.0 settings data migration!"); - this.callMigrators(data, v1_7_0.settingsMigrators); - } - } - - console.log(`Settings data successfully migrated to v${version}!`); + if (isCurrentVersionHigher) { + applyMigrators(settingsMigrators, data, prevVersion); + console.log(`Settings successfully migrated to v${LATEST_VERSION}!`); } } diff --git a/src/system/version_migration/versions/v1_0_4.ts b/src/system/version_migration/versions/v1_0_4.ts index 16bd9db9915..2139352b783 100644 --- a/src/system/version_migration/versions/v1_0_4.ts +++ b/src/system/version_migration/versions/v1_0_4.ts @@ -4,15 +4,18 @@ import { AbilityAttr, defaultStarterSpecies, DexAttr } from "#app/system/game-da import { allSpecies } from "#app/data/pokemon-species"; import { CustomPokemonData } from "#app/data/custom-pokemon-data"; import { isNullOrUndefined } from "#app/utils"; +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; +import type { SettingsSaveMigrator } from "#app/@types/SettingsSaveMigrator"; +import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; -export const systemMigrators = [ - /** - * Migrate ability starter data if empty for caught species. - * @param data {@linkcode SystemSaveData} - */ - function migrateAbilityData(data: SystemSaveData) { +/** + * Migrate ability starter data if empty for caught species. + * @param data - {@linkcode SystemSaveData} + */ +const migrateAbilityData: SystemSaveMigrator = { + version: "1.0.4", + migrate: (data: SystemSaveData): void => { if (data.starterData && data.dexData) { - // biome-ignore lint/complexity/noForEach: Object.keys(data.starterData).forEach(sd => { if (data.dexData[sd]?.caughtAttr && data.starterData[sd] && !data.starterData[sd].abilityAttr) { data.starterData[sd].abilityAttr = 1; @@ -20,12 +23,15 @@ export const systemMigrators = [ }); } }, +}; - /** - * Populate legendary Pokémon statistics if they are missing. - * @param data {@linkcode SystemSaveData} - */ - function fixLegendaryStats(data: SystemSaveData) { +/** + * Populate legendary Pokémon statistics if they are missing. + * @param data - {@linkcode SystemSaveData} + */ +const fixLegendaryStats: SystemSaveMigrator = { + version: "1.0.4", + migrate: (data: SystemSaveData): void => { if ( data.gameStats && data.gameStats.legendaryPokemonCaught !== undefined && @@ -34,7 +40,6 @@ export const systemMigrators = [ data.gameStats.subLegendaryPokemonSeen = 0; data.gameStats.subLegendaryPokemonCaught = 0; data.gameStats.subLegendaryPokemonHatched = 0; - // biome-ignore lint/complexity/noForEach: allSpecies .filter(s => s.subLegendary) .forEach(s => { @@ -66,12 +71,15 @@ export const systemMigrators = [ ); } }, +}; - /** - * Unlock all starters' first ability and female gender option. - * @param data {@linkcode SystemSaveData} - */ - function fixStarterData(data: SystemSaveData) { +/** + * Unlock all starters' first ability and female gender option. + * @param data - {@linkcode SystemSaveData} + */ +const fixStarterData: SystemSaveMigrator = { + version: "1.0.4", + migrate: (data: SystemSaveData): void => { if (!isNullOrUndefined(data.starterData)) { for (const starterId of defaultStarterSpecies) { if (data.starterData[starterId]?.abilityAttr) { @@ -83,17 +91,22 @@ export const systemMigrators = [ } } }, +}; + +export const systemMigrators: Readonly = [ + migrateAbilityData, + fixLegendaryStats, + fixStarterData, ] as const; -export const settingsMigrators = [ - /** - * Migrate from "REROLL_TARGET" property to {@linkcode - * SettingKeys.Shop_Cursor_Target}. - * @param data the `settings` object - */ - - // biome-ignore lint/complexity/noBannedTypes: TODO: fix the type to not be object... - function fixRerollTarget(data: Object) { +/** + * Migrate from `REROLL_TARGET` property to {@linkcode SettingKeys.Shop_Cursor_Target} + * @param data - The `settings` object + */ +const fixRerollTarget: SettingsSaveMigrator = { + version: "1.0.4", + // biome-ignore lint/complexity/noBannedTypes: TODO - refactor settings + migrate: (data: Object): void => { if (data.hasOwnProperty("REROLL_TARGET") && !data.hasOwnProperty(SettingKeys.Shop_Cursor_Target)) { data[SettingKeys.Shop_Cursor_Target] = data["REROLL_TARGET"]; // biome-ignore lint/performance/noDelete: intentional @@ -101,16 +114,20 @@ export const settingsMigrators = [ localStorage.setItem("settings", JSON.stringify(data)); } }, -] as const; +}; -export const sessionMigrators = [ - /** - * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and - * other miscellaneous modifiers (vitamins, White Herb) to any new class - * names and/or change in reload arguments. - * @param data {@linkcode SessionSaveData} - */ - function migrateModifiers(data: SessionSaveData) { +export const settingsMigrators: Readonly = [fixRerollTarget] as const; + +/** + * Converts old lapsing modifiers (battle items, lures, and Dire Hit) and + * other miscellaneous modifiers (vitamins, White Herb) to any new class + * names and/or change in reload arguments. + * @param data - {@linkcode SessionSaveData} + */ +const migrateModifiers: SessionSaveMigrator = { + version: "1.0.4", + // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: necessary? + migrate: (data: SessionSaveData): void => { for (const m of data.modifiers) { if (m.className === "PokemonBaseStatModifier") { m.className = "BaseStatModifier"; @@ -163,12 +180,11 @@ export const sessionMigrators = [ } } }, - /** - * Converts old Pokemon natureOverride and mysteryEncounterData - * to use the new conjoined {@linkcode Pokemon.customPokemonData} structure instead. - * @param data {@linkcode SessionSaveData} - */ - function migrateCustomPokemonDataAndNatureOverrides(data: SessionSaveData) { +}; + +const migrateCustomPokemonData: SessionSaveMigrator = { + version: "1.0.4", + migrate: (data: SessionSaveData): void => { // Fix Pokemon nature overrides and custom data migration for (const pokemon of data.party) { if (pokemon["mysteryEncounterPokemonData"]) { @@ -186,4 +202,6 @@ export const sessionMigrators = [ } } }, -] as const; +}; + +export const sessionMigrators: Readonly = [migrateModifiers, migrateCustomPokemonData] as const; diff --git a/src/system/version_migration/versions/v1_1_0.ts b/src/system/version_migration/versions/v1_1_0.ts deleted file mode 100644 index 5d6247aeaa2..00000000000 --- a/src/system/version_migration/versions/v1_1_0.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const systemMigrators = [] as const; - -export const settingsMigrators = [] as const; - -export const sessionMigrators = [] as const; diff --git a/src/system/version_migration/versions/v1_7_0.ts b/src/system/version_migration/versions/v1_7_0.ts index 167cd974e56..a1213ccf64c 100644 --- a/src/system/version_migration/versions/v1_7_0.ts +++ b/src/system/version_migration/versions/v1_7_0.ts @@ -1,15 +1,18 @@ +import type { SessionSaveMigrator } from "#app/@types/SessionSaveMigrator"; +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { globalScene } from "#app/global-scene"; import { DexAttr, type SessionSaveData, type SystemSaveData } from "#app/system/game-data"; -import * as Utils from "#app/utils"; +import { isNullOrUndefined } from "#app/utils"; -export const systemMigrators = [ - /** - * If a starter is caught, but the only forms registered as caught are not starterSelectable, - * unlock the default form. - * @param data {@linkcode SystemSaveData} - */ - function migrateUnselectableForms(data: SystemSaveData) { +/** + * If a starter is caught, but the only forms registered as caught are not starterSelectable, + * unlock the default form. + * @param data - {@linkcode SystemSaveData} + */ +const migrateUnselectableForms: SystemSaveMigrator = { + version: "1.7.0", + migrate: (data: SystemSaveData): void => { if (data.starterData && data.dexData) { Object.keys(data.starterData).forEach(sd => { const caughtAttr = data.dexData[sd]?.caughtAttr; @@ -30,12 +33,13 @@ export const systemMigrators = [ }); } }, -] as const; +}; -export const settingsMigrators = [] as const; +export const systemMigrators: Readonly = [migrateUnselectableForms] as const; -export const sessionMigrators = [ - function migrateTera(data: SessionSaveData) { +const migrateTera: SessionSaveMigrator = { + version: "1.7.0", + migrate: (data: SessionSaveData): void => { for (let i = 0; i < data.modifiers.length; ) { if (data.modifiers[i].className === "TerastallizeModifier") { data.party.forEach(p => { @@ -63,15 +67,17 @@ export const sessionMigrators = [ } data.party.forEach(p => { - if (Utils.isNullOrUndefined(p.teraType)) { + if (isNullOrUndefined(p.teraType)) { p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; } }); data.enemyParty.forEach(p => { - if (Utils.isNullOrUndefined(p.teraType)) { + if (isNullOrUndefined(p.teraType)) { p.teraType = getPokemonSpeciesForm(p.species, p.formIndex).type1; } }); }, -] as const; +}; + +export const sessionMigrators: Readonly = [migrateTera] as const; diff --git a/src/system/version_migration/versions/v1_8_3.ts b/src/system/version_migration/versions/v1_8_3.ts index d35530c28e9..6e2d96d3673 100644 --- a/src/system/version_migration/versions/v1_8_3.ts +++ b/src/system/version_migration/versions/v1_8_3.ts @@ -1,14 +1,16 @@ +import type { SystemSaveMigrator } from "#app/@types/SystemSaveMigrator"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { DexAttr, type SystemSaveData } from "#app/system/game-data"; import { Species } from "#enums/species"; -export const systemMigrators = [ - /** - * If a starter is caught, but the only forms registered as caught are not starterSelectable, - * unlock the default form. - * @param data {@linkcode SystemSaveData} - */ - function migratePichuForms(data: SystemSaveData) { +/** + * If a starter is caught, but the only forms registered as caught are not starterSelectable, + * unlock the default form. + * @param data - {@linkcode SystemSaveData} + */ +const migratePichuForms: SystemSaveMigrator = { + version: "1.8.3", + migrate: (data: SystemSaveData): void => { if (data.starterData && data.dexData) { // This is Pichu's Pokédex number const sd = 172; @@ -23,8 +25,6 @@ export const systemMigrators = [ } } }, -] as const; +}; -export const settingsMigrators = [] as const; - -export const sessionMigrators = [] as const; +export const systemMigrators: Readonly = [migratePichuForms] as const;