From 0133816ab7d0675d7ff4d2e6b9f827d9dd16daef Mon Sep 17 00:00:00 2001 From: Ben Loe Date: Fri, 9 Aug 2024 17:30:58 -0400 Subject: [PATCH] #8979 Migrate standalone mod components (#8989) * write the standalone component migration using a placeholder V4 type, write tests for the conversion to mods * fix deployment updater test * Migrate form states also * add to strict null checks * fix strict null checks * fix null checks again * fix another test * remove debug log * move migrate up in the tree --------- Co-authored-by: Ben Loe --- src/auth/authUtils.ts | 17 +++ src/background/deploymentUpdater.test.ts | 9 ++ src/background/starterMods.test.ts | 9 ++ .../loadActivationEnhancementsCore.test.ts | 37 +---- ...pStandaloneModDefinitionToModDefinition.ts | 1 + .../useMigrateStandaloneComponentsToMods.ts | 51 +++++++ src/pageEditor/layout/EditorLayout.tsx | 10 ++ src/store/extensionsMigrations.test.ts | 141 ++++++++++++++++++ src/store/extensionsMigrations.ts | 92 ++++++++++-- src/store/extensionsStorage.test.ts | 17 ++- src/store/extensionsStorage.ts | 34 +++-- src/store/extensionsTypes.ts | 21 ++- src/tsconfig.strictNullChecks.json | 2 + 13 files changed, 372 insertions(+), 69 deletions(-) create mode 100644 src/pageEditor/hooks/useMigrateStandaloneComponentsToMods.ts create mode 100644 src/store/extensionsMigrations.test.ts diff --git a/src/auth/authUtils.ts b/src/auth/authUtils.ts index 1bf29e0f0b..1f91f9da24 100644 --- a/src/auth/authUtils.ts +++ b/src/auth/authUtils.ts @@ -23,6 +23,23 @@ import { import { type Me } from "@/data/model/Me"; import selectAuthUserOrganizations from "@/auth/selectAuthUserOrganizations"; import { UserRole } from "@/types/contract"; +import { + readReduxStorage, + validateReduxStorageKey, +} from "@/utils/storageUtils"; +import { type Nullishable } from "@/utils/nullishUtils"; +import { anonAuth } from "@/auth/authConstants"; + +const AUTH_SLICE_STORAGE_KEY = validateReduxStorageKey("persist:authOptions"); + +export async function getUserScope(): Promise> { + const { scope } = await readReduxStorage( + AUTH_SLICE_STORAGE_KEY, + {}, + anonAuth, + ); + return scope; +} export function selectUserDataUpdate({ email, diff --git a/src/background/deploymentUpdater.test.ts b/src/background/deploymentUpdater.test.ts index 600e025465..07ee098e91 100644 --- a/src/background/deploymentUpdater.test.ts +++ b/src/background/deploymentUpdater.test.ts @@ -102,6 +102,15 @@ jest.mock("@/background/installer", () => ({ isUpdateAvailable: jest.fn().mockReturnValue(false), })); +// This comes up in the extensions slice redux-persist migrations that run when mod component state is loaded +jest.mock("@/auth/authUtils", () => { + const actual = jest.requireActual("@/auth/authUtils"); + return { + ...actual, + getUserScope: jest.fn(() => "@test-user"), + }; +}); + const registryFindMock = jest.mocked(registry.find); const isLinkedMock = jest.mocked(isLinked); diff --git a/src/background/starterMods.test.ts b/src/background/starterMods.test.ts index a824eaea75..9bc20b8f14 100644 --- a/src/background/starterMods.test.ts +++ b/src/background/starterMods.test.ts @@ -73,6 +73,15 @@ jest.mock("@/auth/authStorage", () => ({ jest.mock("@/contentScript/messenger/api"); jest.mock("./refreshRegistries"); +// This comes up in the extensions slice redux-persist migrations that run when mod component state is loaded +jest.mock("@/auth/authUtils", () => { + const actual = jest.requireActual("@/auth/authUtils"); + return { + ...actual, + getUserScope: jest.fn(() => "@test-user"), + }; +}); + const isLinkedMock = jest.mocked(isLinked); const refreshRegistriesMock = jest.mocked(refreshRegistries); diff --git a/src/contentScript/loadActivationEnhancementsCore.test.ts b/src/contentScript/loadActivationEnhancementsCore.test.ts index e7df64e40b..1673dda861 100644 --- a/src/contentScript/loadActivationEnhancementsCore.test.ts +++ b/src/contentScript/loadActivationEnhancementsCore.test.ts @@ -20,7 +20,6 @@ import { initSidebarActivation } from "@/contentScript/sidebarActivation"; import { getActivatedModIds } from "@/store/extensionsStorage"; import { getDocument } from "@/starterBricks/starterBrickTestUtils"; import { validateRegistryId } from "@/types/helpers"; -import { type ActivatedModComponent } from "@/types/modComponentTypes"; import { waitForEffect } from "@/testUtils/testHelpers"; import { getActivatingMods } from "@/background/messenger/external/_implementation"; import { @@ -90,19 +89,7 @@ describe("marketplace enhancements", () => { test("given user is logged in, when an activate button is clicked, should open the sidebar", async () => { isLinkedMock.mockResolvedValue(true); window.location.assign(MARKETPLACE_URL); - // Recipe 1 is installed, recipe 2 is not - const modComponent1 = modComponentFactory({ - _recipe: modMetadataFactory({ - id: modId1, - }), - }) as ActivatedModComponent; - const modComponent2 = modComponentFactory() as ActivatedModComponent; - getActivatedModIdsMock.mockResolvedValue( - new Set([ - modComponent1._recipe?.id, - modComponent2._recipe?.id, - ]), - ); + getActivatedModIdsMock.mockResolvedValue(new Set([modId1])); await loadActivationEnhancements(); await initSidebarActivation(); @@ -195,16 +182,7 @@ describe("marketplace enhancements", () => { test("given user is not logged in, when loaded, should change button text", async () => { isLinkedMock.mockResolvedValue(false); window.location.assign(MARKETPLACE_URL); - // Recipe 1 is installed, recipe 2 is not - const modComponent1 = modComponentFactory({ - _recipe: modMetadataFactory({ - id: modId1, - }), - }) as ActivatedModComponent; - const modComponent2 = modComponentFactory() as ActivatedModComponent; - getActivatedModIdsMock.mockResolvedValue( - new Set([modComponent1._recipe?.id, modComponent2._recipe?.id]), - ); + getActivatedModIdsMock.mockResolvedValue(new Set([modId1])); await loadActivationEnhancements(); await initSidebarActivation(); @@ -218,16 +196,7 @@ describe("marketplace enhancements", () => { test("given user is logged in, when loaded, should change button text for installed recipe", async () => { isLinkedMock.mockResolvedValue(true); window.location.assign(MARKETPLACE_URL); - // Recipe 1 is installed, recipe 2 is not - const modComponent1 = modComponentFactory({ - _recipe: modMetadataFactory({ - id: modId1, - }), - }) as ActivatedModComponent; - const modComponent2 = modComponentFactory() as ActivatedModComponent; - getActivatedModIdsMock.mockResolvedValue( - new Set([modComponent1._recipe?.id, modComponent2._recipe?.id]), - ); + getActivatedModIdsMock.mockResolvedValue(new Set([modId1])); await loadActivationEnhancements(); await initSidebarActivation(); diff --git a/src/mods/utils/mapStandaloneModDefinitionToModDefinition.ts b/src/mods/utils/mapStandaloneModDefinitionToModDefinition.ts index ee7cf3ba88..cf77018eef 100644 --- a/src/mods/utils/mapStandaloneModDefinitionToModDefinition.ts +++ b/src/mods/utils/mapStandaloneModDefinitionToModDefinition.ts @@ -24,6 +24,7 @@ import { normalizeSemVerString } from "@/types/helpers"; /** * Map a standalone mod definition from the server to a mod definition * @see mapModComponentToUnsavedModDefinition + * @see createModMetadataForStandaloneComponent - similar functionality */ export function mapStandaloneModDefinitionToModDefinition( standaloneModDefinition: StandaloneModDefinition, diff --git a/src/pageEditor/hooks/useMigrateStandaloneComponentsToMods.ts b/src/pageEditor/hooks/useMigrateStandaloneComponentsToMods.ts new file mode 100644 index 0000000000..51556ce493 --- /dev/null +++ b/src/pageEditor/hooks/useMigrateStandaloneComponentsToMods.ts @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2024 PixieBrix, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { useDispatch, useSelector } from "react-redux"; +import { selectModComponentFormStates } from "@/pageEditor/store/editor/editorSelectors"; +import { selectActivatedModComponents } from "@/store/extensionsSelectors"; +import { useEffect } from "react"; +import { actions } from "@/pageEditor/store/editor/editorSlice"; + +/** + * Note: This must be run underneath the PersistGate component in the React component tree + */ +export default function useMigrateStandaloneComponentsToMods() { + const dispatch = useDispatch(); + const formStates = useSelector(selectModComponentFormStates); + const activatedModComponents = useSelector(selectActivatedModComponents); + + useEffect(() => { + const standaloneComponentFormStates = formStates.filter( + (formState) => formState.modMetadata == null, + ); + + for (const formState of standaloneComponentFormStates) { + const modMetadata = activatedModComponents.find( + ({ id }) => id === formState.uuid, + )?._recipe; + + if (modMetadata == null) { + dispatch(actions.removeModComponentFormState(formState.uuid)); + } else { + formState.modMetadata = modMetadata; + dispatch(actions.syncModComponentFormState(formState)); + } + } + // eslint-disable-next-line -- Only need to run this migration once + }, []); +} diff --git a/src/pageEditor/layout/EditorLayout.tsx b/src/pageEditor/layout/EditorLayout.tsx index c8ba86c71d..023b419cf4 100644 --- a/src/pageEditor/layout/EditorLayout.tsx +++ b/src/pageEditor/layout/EditorLayout.tsx @@ -34,6 +34,7 @@ import { selectIsStaleSession } from "@/store/sessionChanges/sessionChangesSelec import StaleSessionPane from "@/pageEditor/panes/StaleSessionPane"; import { actions as editorActions } from "@/pageEditor/store/editor/editorSlice"; import { usePreviousValue } from "@/hooks/usePreviousValue"; +import useMigrateStandaloneComponentsToMods from "@/pageEditor/hooks/useMigrateStandaloneComponentsToMods"; const EditorLayout: React.FunctionComponent = () => { const dispatch = useDispatch(); @@ -45,6 +46,15 @@ const EditorLayout: React.FunctionComponent = () => { const url = useCurrentInspectedUrl(); + /** + * Migrate form states for activated standalone mod components. We are + * running it here because it's the top-most component underneath + * the redux-persist PersistGate. + * + * @since 2.0.8 + */ + useMigrateStandaloneComponentsToMods(); + const currentPane = useMemo( () => isRestricted ? ( diff --git a/src/store/extensionsMigrations.test.ts b/src/store/extensionsMigrations.test.ts new file mode 100644 index 0000000000..0a63730886 --- /dev/null +++ b/src/store/extensionsMigrations.test.ts @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2024 PixieBrix, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import { + createModMetadataForStandaloneComponent, + migrateStandaloneComponentsToMods, +} from "@/store/extensionsMigrations"; +import { + activatedModComponentFactory, + modMetadataFactory, +} from "@/testUtils/factories/modComponentFactories"; +import { + autoUUIDSequence, + timestampFactory, +} from "@/testUtils/factories/stringFactories"; +import { toLower } from "lodash"; + +const testUserScope = "@test-user"; + +describe("createModMetadataForStandaloneComponent", () => { + it("creates mod metadata for standalone component", () => { + const componentId = autoUUIDSequence(); + const componentLabel = "My Test Mod Component"; + const componentUpdateTimestamp = timestampFactory(); + const component = activatedModComponentFactory({ + id: componentId, + label: componentLabel, + updateTimestamp: componentUpdateTimestamp, + }); + expect( + createModMetadataForStandaloneComponent(component, testUserScope), + ).toEqual({ + ...component, + _recipe: { + id: `${testUserScope}/converted/${toLower(componentId)}`, + name: componentLabel, + version: "1.0.0", + description: "Page Editor mod automatically converted to a package", + sharing: { + public: false, + organizations: [], + }, + updated_at: componentUpdateTimestamp, + }, + }); + }); +}); + +describe("migrateStandaloneComponentsToMods", () => { + it("does not throw error when extensions is empty", () => { + expect(migrateStandaloneComponentsToMods([], testUserScope)).toEqual([]); + }); + + it("returns mod components when there are no standalone components", () => { + const modMetadata = modMetadataFactory(); + const modComponents = [ + activatedModComponentFactory({ + _recipe: modMetadata, + }), + activatedModComponentFactory({ + _recipe: modMetadata, + }), + activatedModComponentFactory({ + _recipe: modMetadata, + }), + ]; + + expect( + migrateStandaloneComponentsToMods(modComponents, testUserScope), + ).toEqual(modComponents); + }); + + it("returns only mod components when userScope is null", () => { + const modMetadata = modMetadataFactory(); + const modComponents = [ + activatedModComponentFactory({ + _recipe: modMetadata, + }), + activatedModComponentFactory({ + _recipe: modMetadata, + }), + activatedModComponentFactory({ + _recipe: modMetadata, + }), + ]; + const standaloneComponents = [ + activatedModComponentFactory(), + activatedModComponentFactory(), + ]; + + expect( + migrateStandaloneComponentsToMods( + [...modComponents, ...standaloneComponents], + null, + ), + ).toEqual(modComponents); + }); + + it("converts standalone components correctly", () => { + const modMetadata = modMetadataFactory(); + const modComponents = [ + activatedModComponentFactory({ + _recipe: modMetadata, + }), + activatedModComponentFactory({ + _recipe: modMetadata, + }), + activatedModComponentFactory({ + _recipe: modMetadata, + }), + ]; + const standaloneComponents = [ + activatedModComponentFactory(), + activatedModComponentFactory(), + ]; + const migratedStandaloneComponents = standaloneComponents.map((component) => + createModMetadataForStandaloneComponent(component, testUserScope), + ); + + expect( + migrateStandaloneComponentsToMods( + [...modComponents, ...standaloneComponents], + testUserScope, + ), + ).toEqual([...modComponents, ...migratedStandaloneComponents]); + }); +}); diff --git a/src/store/extensionsMigrations.ts b/src/store/extensionsMigrations.ts index 733efa4706..10400b1226 100644 --- a/src/store/extensionsMigrations.ts +++ b/src/store/extensionsMigrations.ts @@ -17,27 +17,33 @@ import { type MigrationManifest, type PersistedState } from "redux-persist"; import { - type ModComponentStateVersions, - type ModComponentStateV0, - type ModComponentStateV1, - type ModComponentStateV2, - type ModComponentStateV3, isModComponentStateV0, isModComponentStateV1, isModComponentStateV2, isModComponentStateV3, + type ModComponentStateLegacyVersions, + type ModComponentStateV0, + type ModComponentStateV1, + type ModComponentStateV2, + type ModComponentStateV3, + type ModComponentStateV4, } from "@/store/extensionsTypes"; -import { omit } from "lodash"; +import { omit, partition, toLower } from "lodash"; import { migrateIntegrationDependenciesV1toV2 } from "@/store/editorMigrations"; import { nowTimestamp } from "@/utils/timeUtils"; +import { type Nullishable } from "@/utils/nullishUtils"; +import { type ActivatedModComponentV2 } from "@/types/modComponentTypes"; +import { normalizeSemVerString, validateRegistryId } from "@/types/helpers"; +import { getUserScope } from "@/auth/authUtils"; -export const migrations: MigrationManifest = { +// eslint-disable-next-line local-rules/persistBackgroundData -- This is never mutated +const migrations: MigrationManifest = { // Redux-persist defaults to version: -1; Initialize to 0-indexed // state version to match state type names and existing versions // The typeguards shouldn't be necessary, but in certain cases, the rehydration can run // on ModComponentStateV2 extensions before the _persist key is added 0: (state) => state, - 1(state: ModComponentStateVersions & PersistedState) { + 1(state: ModComponentStateLegacyVersions & PersistedState) { if (isModComponentStateV0(state)) { return migrateModComponentStateV0ToV1(state); } @@ -58,8 +64,18 @@ export const migrations: MigrationManifest = { return state; }, + // V4 migration defined below }; +export async function createMigrationsManifest(): Promise { + const userScope = await getUserScope(); + return { + ...migrations, + 4: (state: ModComponentStateV3 & PersistedState) => + migrateModComponentStateV3toV4(state, userScope), + }; +} + function migrateModComponentStateV0ToV1( state: ModComponentStateV0 & PersistedState, ): ModComponentStateV1 & PersistedState { @@ -100,8 +116,66 @@ function migrateModComponentStateV2toV3( }; } +/** + * Exported for testing only + * + * @see mapStandaloneModDefinitionToModDefinition - similar functionality + */ +export function createModMetadataForStandaloneComponent( + extension: ActivatedModComponentV2, + userScope: string, +): ActivatedModComponentV2 { + return { + ...extension, + _recipe: { + id: validateRegistryId(`${userScope}/converted/${toLower(extension.id)}`), + name: extension.label, + version: normalizeSemVerString("1.0.0"), + description: "Page Editor mod automatically converted to a package", + sharing: { + public: false, + organizations: [], + }, + updated_at: extension.updateTimestamp, + }, + }; +} + +/** + * Exported for testing only + */ +export function migrateStandaloneComponentsToMods( + extensions: ActivatedModComponentV2[], + userScope: Nullishable, +): ActivatedModComponentV2[] { + const [modComponents, standaloneComponents] = partition( + extensions, + (extension) => Boolean(extension._recipe), + ); + + if (userScope == null || standaloneComponents.length === 0) { + return modComponents; + } + + const convertedStandaloneComponents = standaloneComponents.map((extension) => + createModMetadataForStandaloneComponent(extension, userScope), + ); + + return [...modComponents, ...convertedStandaloneComponents]; +} + +function migrateModComponentStateV3toV4( + state: ModComponentStateV3 & PersistedState, + userScope: Nullishable, +): ModComponentStateV4 & PersistedState { + return { + ...state, + extensions: migrateStandaloneComponentsToMods(state.extensions, userScope), + }; +} + export function inferModComponentStateVersion( - state: ModComponentStateVersions, + state: ModComponentStateLegacyVersions, ): number { if (isModComponentStateV3(state)) { return 3; diff --git a/src/store/extensionsStorage.test.ts b/src/store/extensionsStorage.test.ts index 1feee37ed9..a3093e54bf 100644 --- a/src/store/extensionsStorage.test.ts +++ b/src/store/extensionsStorage.test.ts @@ -16,8 +16,8 @@ */ import { + createMigrationsManifest, inferModComponentStateVersion, - migrations, } from "@/store/extensionsMigrations"; import { initialState } from "@/store/extensionsSliceInitialState"; import { @@ -44,13 +44,21 @@ const inferModComponentStateVersionMock = jest.mocked( inferModComponentStateVersion, ); +jest.mock("@/auth/authUtils", () => { + const actual = jest.requireActual("@/auth/authUtils"); + return { + ...actual, + getUserScope: jest.fn(() => "@testUser"), + }; +}); + const STORAGE_KEY = validateReduxStorageKey("persist:extensionOptions"); describe("getModComponentState", () => { test("readReduxStorage is called with inferModComponentStateVersion", async () => { - void getModComponentState(); + await getModComponentState(); expect(readReduxStorageMock).toHaveBeenCalledWith( STORAGE_KEY, - migrations, + expect.toBeObject(), // Mod component slice migrations are dynamically created initialState, inferModComponentStateVersionMock, ); @@ -58,7 +66,8 @@ describe("getModComponentState", () => { }); describe("persistExtensionOptionsConfig", () => { - test("version is the highest migration version", () => { + test("version is the highest migration version", async () => { + const migrations = await createMigrationsManifest(); const maxVersion = getMaxMigrationsVersion(migrations); expect(persistModComponentOptionsConfig.version).toBe(maxVersion); }); diff --git a/src/store/extensionsStorage.ts b/src/store/extensionsStorage.ts index 09b8ef6939..3c2f2ac89d 100644 --- a/src/store/extensionsStorage.ts +++ b/src/store/extensionsStorage.ts @@ -18,13 +18,12 @@ import { localStorage } from "redux-persist-webextension-storage"; import { createMigrate } from "redux-persist"; import { + createMigrationsManifest, inferModComponentStateVersion, - migrations, } from "@/store/extensionsMigrations"; import { type ModComponentState } from "./extensionsTypes"; import { type StorageInterface } from "@/store/StorageInterface"; import { type RegistryId } from "@/types/registryTypes"; -import { compact, isEmpty } from "lodash"; import { boolean } from "@/utils/typeUtils"; import { readReduxStorage, @@ -33,10 +32,13 @@ import { } from "@/utils/storageUtils"; import { getMaxMigrationsVersion } from "@/store/migratePersistedState"; import { initialState } from "@/store/extensionsSliceInitialState"; +import { type PersistMigrate } from "redux-persist/es/types"; +import { compact } from "lodash"; const STORAGE_KEY = validateReduxStorageKey("persist:extensionOptions"); export async function getModComponentState(): Promise { + const migrations = await createMigrationsManifest(); return readReduxStorage( STORAGE_KEY, migrations, @@ -48,18 +50,9 @@ export async function getModComponentState(): Promise { /** * Returns the set of currently activated mod ids. Reads current activated mods from storage. */ -export async function getActivatedModIds(): Promise< - Set -> { - const modComponentState = await getModComponentState(); - - if (isEmpty(modComponentState?.extensions)) { - return new Set(); - } - - return new Set( - compact(modComponentState.extensions.map(({ _recipe }) => _recipe?.id)), - ); +export async function getActivatedModIds(): Promise> { + const { extensions = [] } = await getModComponentState(); + return new Set(compact(extensions.map(({ _recipe }) => _recipe?.id))); } /** @@ -68,6 +61,7 @@ export async function getActivatedModIds(): Promise< export async function saveModComponentState( state: ModComponentState, ): Promise { + const migrations = await createMigrationsManifest(); await setReduxStorage( STORAGE_KEY, state, @@ -75,12 +69,20 @@ export async function saveModComponentState( ); } +const migrate: PersistMigrate = async (state, currentVersion) => { + const migrations = await createMigrationsManifest(); + const migrator = createMigrate(migrations, { + debug: boolean(process.env.DEBUG), + }); + return migrator(state, currentVersion); +}; + export const persistModComponentOptionsConfig = { key: "extensionOptions", // Change the type of localStorage to our overridden version so that it can be exported // See: @/store/StorageInterface.ts storage: localStorage as StorageInterface, - version: 3, + version: 4, // https://github.com/rt2zz/redux-persist#migrations - migrate: createMigrate(migrations, { debug: boolean(process.env.DEBUG) }), + migrate, }; diff --git a/src/store/extensionsTypes.ts b/src/store/extensionsTypes.ts index 74a2498b24..a72f01041d 100644 --- a/src/store/extensionsTypes.ts +++ b/src/store/extensionsTypes.ts @@ -55,25 +55,34 @@ export type ModComponentStateV3 = { extensions: ActivatedModComponentV2[]; }; -export type ModComponentStateVersions = +export type ModComponentStateLegacyVersions = | ModComponentStateV0 | ModComponentStateV1 | ModComponentStateV2 | ModComponentStateV3; -export type ModComponentState = ModComponentStateV3; + +/** + * @deprecated - Do not use versioned state types directly + * + * @since 2.0.8 This type is only being used as a placeholder for the + * migration to convert all stand-alone mod components to mod definitions + */ +export type ModComponentStateV4 = ModComponentStateV3; + +export type ModComponentState = ModComponentStateV4; export type ModComponentsRootState = { options: ModComponentState; }; export function isModComponentStateV0( - state: ModComponentStateVersions, + state: ModComponentStateLegacyVersions, ): state is ModComponentStateV0 { return !Array.isArray(state.extensions); } export function isModComponentStateV1( - state: ModComponentStateVersions, + state: ModComponentStateLegacyVersions, ): state is ModComponentStateV1 { return ( Array.isArray(state.extensions) && @@ -83,7 +92,7 @@ export function isModComponentStateV1( } export function isModComponentStateV2( - state: ModComponentStateVersions, + state: ModComponentStateLegacyVersions, ): state is ModComponentStateV2 { return ( Array.isArray(state.extensions) && @@ -95,7 +104,7 @@ export function isModComponentStateV2( } export function isModComponentStateV3( - state: ModComponentStateVersions, + state: ModComponentStateLegacyVersions, ): state is ModComponentStateV3 { return ( Array.isArray(state.extensions) && diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 0e6827e7a1..3987e91725 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -1259,6 +1259,7 @@ "./pageEditor/hooks/useCurrentInspectedUrl.ts", "./pageEditor/hooks/useDeactivateMod.tsx", "./pageEditor/hooks/useEscapeHandler.ts", + "./pageEditor/hooks/useMigrateStandaloneComponentsToMods.ts", "./pageEditor/hooks/useModComponentTrace.ts", "./pageEditor/hooks/useRemoveModComponentFromStorage.test.ts", "./pageEditor/hooks/useRemoveModComponentFromStorage.tsx", @@ -1602,6 +1603,7 @@ "./store/enterprise/singleSignOn.ts", "./store/enterprise/useManagedStorageState.test.ts", "./store/enterprise/useManagedStorageState.ts", + "./store/extensionsMigrations.test.ts", "./store/extensionsMigrations.ts", "./store/extensionsSelectors.ts", "./store/extensionsSlice.ts",