Skip to content

Ecosystem merge#2113

Merged
ebkr merged 20 commits into
developfrom
ecosystem-merge
Apr 28, 2026
Merged

Ecosystem merge#2113
ebkr merged 20 commits into
developfrom
ecosystem-merge

Conversation

@ebkr
Copy link
Copy Markdown
Owner

@ebkr ebkr commented Apr 8, 2026

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

  • By default, we look for a merge file
    • If no merge file, we fall back to the bundled schema
  • Once we have loaded the schema, we (theoretically*) kick off an update to pull the latest schema
    • *This is actually just a stub for now as this functionality will be implemented later
  • Retrofit schema changes to applicable usages

TL;DR for reviews

  • Most of the work happens in EcosystemSchema.ts
  • ThunderstoreSchema.ts changes add reactive variables which can be referenced nicely inside of a Vue context. This means we can auto-update the game list as soon as we get a result without having to trigger an internal event.
    • Since all usages now use these reactive variables, we no longer have a need for the overall ThunderstoreSchema class. The file has been kept, but mostly as a holder for the variables and to keep the re-export functionality that it previously had. Out of scope to change that behaviour.
  • There is a boot file which is effectively a preload script. It happens before the screens are hit.
  • For the most part, the bundle loading logic was actually just copied sideways from the old implementation.
  • A couple of test fixes + new tests
    • Test fixes with ! changes are just to reduce type errors in the files

@ebkr ebkr marked this pull request as ready for review April 9, 2026 08:46
Comment thread src/boot/ecosystem.ts
Comment thread src/r2mm/ecosystem/EcosystemSchema.ts Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts to 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.

Comment thread src/boot/ecosystem.ts
Comment on lines +8 to +11
FsProvider.provide(() => NodeFsImplementation);
await updateEcosystemReactives();
// @ts-ignore
FsProvider.provide(() => undefined);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/r2mm/ecosystem/EcosystemSchema.ts Outdated
Comment on lines +74 to +88
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;
Comment on lines +18 to +20
async function getMergedEcosystemPath(): Promise<string> {
return path.join(PathResolver.ROOT, "latest-ecosystem-schema.json");
}
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated PR description

Comment thread src/r2mm/ecosystem/EcosystemSchema.ts Outdated
Comment on lines +43 to +48
async function loadBundledSchema(): Promise<ThunderstoreEcosystem> {
const ajv = new Ajv();
addFormats(ajv);

const validate = ajv.compile(jsonSchema);
const isOk = validate(bundledEcosystem);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Infrequent access and only used on resolve if the online schema failed to fetch. Not an issue.

Comment on lines +28 to +37
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
])
);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

ebkr added 13 commits April 15, 2026 10:48
…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.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.ts to load/resolve cached vs bundled schema and update reactive exports.
  • Replaced EcosystemSchema class 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';
Comment on lines +74 to +80
try {
let content = await getLastSavedEcosystemSchema();
if (!new VersionNumber(content.version).isEqualTo(ManagerInformation.VERSION)) {
return bundledSchema();
}
return content;
} catch (e) {
Comment on lines +81 to +86
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();
Comment thread src/r2mm/ecosystem/EcosystemSchema.ts Outdated
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";
Comment on lines 44 to +52
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";
Copy link
Copy Markdown
Collaborator

@ethangreen-dev ethangreen-dev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, nice work as always.

@ebkr ebkr merged commit da7d177 into develop Apr 28, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants