Ecosystem merge#2113
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Introduces a new ecosystem schema loading pathway that can hydrate Vue-reactive schema state from either a bundled schema or a cached schema on disk, and updates schema consumers/tests to use the new reactive sources.
Changes:
- Added
EcosystemSchema.tsto load/validate bundled schema, read an on-disk cached schema, and populate reactive exports. - Refactored schema consumers (game list, install rules, modloader variants) to read from reactive schema refs instead of a static class.
- Added a Quasar boot step plus unit test updates/new tests to ensure the ecosystem reactives are initialized before use.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/r2mm/ecosystem/EcosystemSchema.ts |
New loader/validator + cache resolution that populates reactive ecosystem state |
src/model/schema/ThunderstoreSchema.ts |
Replaces static schema class with Vue ref exports for supported games and modloader packages |
src/r2mm/installing/profile_installers/ModLoaderVariantRecord.ts |
Reworks modloader exports to be derived from reactive schema state |
src/r2mm/installing/InstallationRules.ts |
Uses reactive supported games to build rules |
src/model/game/GameManager.ts |
Uses reactive game list source |
src/boot/ecosystem.ts |
Boot-time initialization/hydration of ecosystem reactives |
quasar.config.ts |
Registers new ecosystem boot file |
src/pages/GameSelectionScreen.vue |
Adjusts flow to return after proceed() and stubs schema update call |
test/vitest/tests/unit/EcosystemSchema/EcosystemSchema.spec.ts |
Adds coverage for cache fallback/version match and writing schema to disk |
Various test/vitest/... specs/utils |
Ensures ecosystem reactives are initialized for unit tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| FsProvider.provide(() => NodeFsImplementation); | ||
| await updateEcosystemReactives(); | ||
| // @ts-ignore | ||
| FsProvider.provide(() => undefined); |
There was a problem hiding this comment.
This doesn't cause any issues because the intent is to set it when needed, then allow it to be re-set as part of the ordinary workflow.
A separate PR addresses the concern and removes the need for the unset.
| let content: VersionedThunderstoreEcosystem; | ||
| try { | ||
| content = await getLastSavedEcosystemSchema(); | ||
| } catch (e) { | ||
| const err = e as unknown as Error; | ||
| LoggerProvider.instance.Log( | ||
| LogSeverity.ERROR, | ||
| `Failed to load cached ecosystem schema, falling back to bundled schema\n${err.message}` | ||
| ); | ||
| return bundledSchema(); | ||
| } | ||
| if (!new VersionNumber(content.version).isEqualTo(ManagerInformation.VERSION)) { | ||
| return bundledSchema(); | ||
| } | ||
| return content; |
| async function getMergedEcosystemPath(): Promise<string> { | ||
| return path.join(PathResolver.ROOT, "latest-ecosystem-schema.json"); | ||
| } |
| async function loadBundledSchema(): Promise<ThunderstoreEcosystem> { | ||
| const ajv = new Ajv(); | ||
| addFormats(ajv); | ||
|
|
||
| const validate = ajv.compile(jsonSchema); | ||
| const isOk = validate(bundledEcosystem); |
There was a problem hiding this comment.
Infrequent access and only used on resolve if the online schema failed to fetch. Not an issue.
| MODLOADER_PACKAGES = EcosystemModloaderPackages.value.map((x) => | ||
| new ModLoaderPackageMapping(x.packageId, x.rootFolder, x.loader) | ||
| ); | ||
| MOD_LOADER_VARIANTS = Object.fromEntries( | ||
| EcosystemSupportedGames.value | ||
| .map(([_, game]) => [ | ||
| game.internalFolderName, | ||
| OVERRIDES[game.internalFolderName] || MODLOADER_PACKAGES | ||
| ]) | ||
| ); |
There was a problem hiding this comment.
Not an issue because as part of the Quasar boot files, we initialise this.
It isn't until render + load of the GameSelectionScreen.vue that this is then applicable.
…s for ecosystem. # Conflicts: # src/model/schema/ThunderstoreSchema.ts
…tions post-boot. - Start with bundled - Next, load from disk - Finally, should query live (not done yet)
- Fixed error caused by fetchLatestSchema stub not including valid keys - Changed getMergedEcosystemPath to be more viable - Enabled updateLatestMergedEcosystemSchema call in GameSelectionScreen
- Also removed EcosystemSchema class as no longer needed - Replaced usages of EcosystemSchema getters with reactive equivalents.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new ecosystem schema loading flow that can populate the app’s schema data from a bundled JSON file or a cached latest-ecosystem-schema.json on disk, and retrofits consumers to read from reactive schema values.
Changes:
- Added
src/r2mm/ecosystem/EcosystemSchema.tsto load/resolve cached vs bundled schema and update reactive exports. - Replaced
EcosystemSchemaclass usage with reactive refs (EcosystemSupportedGames,EcosystemModloaderPackages) and helper functions across core logic. - Added Quasar boot initialization for ecosystem schema and updated/added Vitest coverage around the new schema behavior.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| test/vitest/utils/InstallLogicUtils.ts | Initializes ecosystem reactives in shared installer test setup. |
| test/vitest/tests/unit/Profile/Profile.ts.spec.ts | Updates tests to initialize ecosystem reactives before using GameManager.gameList. |
| test/vitest/tests/unit/ModLinker/ModLinker.spec.ts | Adds ecosystem reactive initialization to prevent empty game list. |
| test/vitest/tests/unit/MelonLoader/state.spec.ts | Adds ecosystem initialization and adjusts type assertions in tests. |
| test/vitest/tests/unit/Installers/ModLoader/Installer.Tests.spec.ts | Switches to getGameConfigBySettingsIdentifier function API. |
| test/vitest/tests/unit/GameDirectoryResolver/GameDirectoryResolver.spec.ts | Ensures ecosystem reactives are initialized for game list lookups. |
| test/vitest/tests/unit/EcosystemSchema/EcosystemSchema.spec.ts | Adds new unit tests for cache/bundled loading + file writing. |
| src/r2mm/installing/profile_installers/ModLoaderVariantRecord.ts | Reworks mod loader exports to derive from reactive ecosystem refs. |
| src/r2mm/installing/InstallationRules.ts | Uses reactive supported games when building installation rules. |
| src/r2mm/ecosystem/EcosystemSchema.ts | Implements schema resolution (cached vs bundled) and reactive updates. |
| src/pages/GameSelectionScreen.vue | Wires (stubbed) ecosystem update hook and adjusts control flow. |
| src/model/schema/ThunderstoreSchema.ts | Replaces EcosystemSchema class with reactive refs + helper function. |
| src/model/game/GameManager.ts | Builds gameList from reactive supported games. |
| src/installers/InstallRulePluginInstaller.ts | Switches to getGameConfigBySettingsIdentifier function API. |
| src/boot/ecosystem.ts | Adds Quasar boot-time initialization for ecosystem reactives. |
| quasar.config.ts | Registers new ecosystem boot file. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import * as path from 'path'; | ||
| import {beforeEach, describe, expect, test} from 'vitest'; | ||
|
|
||
| import {VersionedThunderstoreEcosystem, updateEcosystemReactives, updateLatestEcosystemSchema} from 'src/r2mm/ecosystem/EcosystemSchema'; |
| try { | ||
| let content = await getLastSavedEcosystemSchema(); | ||
| if (!new VersionNumber(content.version).isEqualTo(ManagerInformation.VERSION)) { | ||
| return bundledSchema(); | ||
| } | ||
| return content; | ||
| } catch (e) { |
| const err = e as unknown as Error; | ||
| LoggerProvider.instance.Log( | ||
| LogSeverity.ERROR, | ||
| `Failed to load cached ecosystem schema, falling back to bundled schema\n${err.message}` | ||
| ); | ||
| return bundledSchema(); |
| import ManagerInformation from "../../_managerinf/ManagerInformation"; | ||
| import {EcosystemModloaderPackages, EcosystemSupportedGames} from "../../model/schema/ThunderstoreSchema"; | ||
| import {updateModLoaderExports} from "../installing/profile_installers/ModLoaderVariantRecord"; | ||
| import LoggerProvider, {LogSeverity} from "src/providers/ror2/logging/LoggerProvider"; |
| describe("State file", () => { | ||
|
|
||
| beforeAll(async () => { | ||
| const inMemoryFs = new InMemoryFsProvider(); | ||
| FsProvider.provide(() => inMemoryFs); | ||
| InMemoryFsProvider.clear(); | ||
| await updateEcosystemReactives(); | ||
| }) | ||
|
|
| import { beforeAll, describe, expect, test } from 'vitest'; | ||
| import { providePathImplementation } from '../../../../../src/providers/node/path/path'; | ||
| import { TestPathProvider } from '../../../stubs/providers/node/Node.Path.Provider'; | ||
| import { updateEcosystemReactives } from "src/r2mm/ecosystem/EcosystemSchema"; |
| import InMemoryFsProvider from '../../../stubs/providers/InMemory.FsProvider'; | ||
| import { providePathImplementation } from '../../../../../src/providers/node/path/path'; | ||
| import { TestPathProvider } from '../../../stubs/providers/node/Node.Path.Provider'; | ||
| import {updateEcosystemReactives} from "src/r2mm/ecosystem/EcosystemSchema"; |
| import { State } from '../store'; | ||
| import { useRouter } from 'vue-router'; | ||
| import ProtocolProvider from '../providers/generic/protocol/ProtocolProvider'; | ||
| import {updateEcosystemReactives, updateLatestEcosystemSchema} from "src/r2mm/ecosystem/EcosystemSchema"; |
| import {providePathImplementation} from "../../../../../src/providers/node/path/path"; | ||
| import {TestPathProvider} from "../../../stubs/providers/node/Node.Path.Provider"; | ||
| import StubProfileProvider from '../../../stubs/providers/stub.ProfileProvider'; | ||
| import {updateEcosystemReactives} from "src/r2mm/ecosystem/EcosystemSchema"; |
| import { TestPathProvider } from '../../../stubs/providers/node/Node.Path.Provider'; | ||
| import { provideAppWindowImplementation } from '../../../../../src/providers/node/app/app_window'; | ||
| import { TestAppWindowProvider } from '../../../stubs/providers/node/AppWindow.Provider'; | ||
| import {updateEcosystemReactives} from "src/r2mm/ecosystem/EcosystemSchema"; |
ethangreen-dev
left a comment
There was a problem hiding this comment.
Looks good, nice work as always.
Ecosystem Merge
Adds support for loading from bundled ecosystem-schema.json, as well as loading from a latest-ecosystem-schema.json which can exist on disk
Process
TL;DR for reviews
!changes are just to reduce type errors in the files