@bscotch/stitch-launcher
Version:
Manage GameMaker IDE and runtime installations for fast switching between versions.
202 lines • 7.86 kB
JavaScript
import { __decorate, __metadata } from "tslib";
import { gameMakerReleaseWithNotesSchema, releasesUrl, runtimeFeedUrls, } from '@bscotch/gamemaker-releases';
import { pathy } from '@bscotch/pathy';
import { MaxAge } from '@bscotch/utility/browser';
import { z } from 'zod';
import { gameMakerUserDataSchema, } from './GameMakerLauncher.types.js';
import { GameMakerUser } from './GameMakerUser.js';
import { cleanVersionString, downloadIfCacheExpired, listGameMakerDataDirs, listInstalledIdes, listRuntimeFeedsConfigPaths, stitchConfigDir, } from './utility.js';
export class GameMakerComponent {
info;
constructor(info) {
this.info = info;
}
get version() {
return this.info.version;
}
get channel() {
return this.info.channel;
}
get executablePath() {
return this.info.executablePath;
}
get directory() {
return this.info.directory;
}
get publishedAt() {
return this.info.publishedAt;
}
get usersDirectory() {
return pathy(GameMakerComponent.userDirectories[this.channel || 'beta']);
}
async activeUser() {
const userInfoFilePath = this.usersDirectory.join('um.json');
return new GameMakerUser(await userInfoFilePath.read({
schema: gameMakerUserDataSchema,
fallback: {},
}));
}
async activeUserDirectory() {
const userInfo = await this.activeUser();
return this.usersDirectory.join(userInfo.directoryBasename);
}
/**
* List paths to important files and directories.
*/
static async listWellKnownPaths(options) {
const known = [];
const [dataDirs, ideExes] = await Promise.all([
listGameMakerDataDirs(),
listInstalledIdes(options?.programFiles),
]);
for (const dir of dataDirs) {
known.push({
id: 'gameMakerDataDir',
path: dir.absolute,
name: 'IDE: Data',
description: 'The directory in which all GameMaker data is stored.',
});
known.push({
id: 'runtimeFeedsConfigFile',
path: dir.join('runtime_feeds.json').absolute,
name: 'Runtime: Feeds',
description: 'The list of Runtime Feed URLs used by the IDE to find and display allowed runtimes.',
});
known.push({
id: 'activeRuntimeConfigFile',
path: dir.join('runtime.json').absolute,
name: 'Runtime: Active',
description: 'The active runtime to be used by the IDE.',
});
known.push({
id: 'defaultMacrosFile',
path: dir.join('default_macros.json').absolute,
name: 'IDE: Defaults',
description: `IDE configuration defaults, including URLs for IDE feeds.`,
});
known.push({
id: 'runtimesCacheDir',
path: dir.join('Cache/runtimes').absolute,
name: 'Runtime: Downloads',
description: `The local cache for downloaded GameMaker Runtimes.`,
});
known.push({
id: 'uiLogFile',
path: dir.join('ui.log').absolute,
name: 'IDE: UI Logs',
description: `Logs created by the GameMaker IDE during its most recent run.`,
});
}
for (const exe of ideExes) {
known.push({
id: 'gameMakerIdeDir',
path: exe.up().absolute,
name: 'IDE: Installation Directory',
description: 'Where the GameMaker installer installs the IDE.',
});
known.push({
id: 'gameMakerIdeExe',
path: exe.absolute,
name: 'IDE: Executable',
description: 'The GameMaker IDE executable.',
});
known.push({
id: 'initialDefaultMacrosFile',
path: exe.up().join('defaults/default_macros.json').absolute,
name: 'IDE: Initial Defaults',
description: `IDE configuration defaults, which can be overridden in the program data's default_macros.json file.`,
});
}
const userDirs = GameMakerComponent.userDirectories;
for (const [, dir] of Object.entries(userDirs)) {
const path = pathy(dir);
if (!(await path.exists())) {
continue;
}
known.push({
id: 'gameMakerUserDir',
path: path.absolute,
name: `User: Directory`,
description: `Where GameMaker stores login and related data.`,
});
known.push({
id: 'activeUserFile',
path: path.join('um.json').absolute,
name: 'User: Active',
description: `Information about the most recently active user.`,
});
}
return known;
}
static get releasesCachePath() {
return GameMakerComponent.cacheDir
.join(`releases-summary.json`)
.withValidator(z.array(gameMakerReleaseWithNotesSchema));
}
static async listReleases(options) {
await downloadIfCacheExpired(releasesUrl, GameMakerComponent.releasesCachePath, options?.maxAgeSeconds || 1800);
return await GameMakerComponent.releasesCachePath.read();
}
static async findRelease(searchOptions) {
const releases = await GameMakerComponent.listReleases({
maxAgeSeconds: searchOptions.maxAgeSeconds,
});
const release = releases.find((release) => {
if (searchOptions.ideVersion) {
return (release.ide.version === cleanVersionString(searchOptions.ideVersion));
}
else if (searchOptions.runtimeVersion) {
return (release.runtime.version ===
cleanVersionString(searchOptions.runtimeVersion));
}
return false;
});
return release;
}
static get userDirectories() {
const prefix = `${process.env.APPDATA}/GameMakerStudio2`;
return {
lts: `${prefix}-LTS`,
stable: prefix,
beta: `${prefix}-Beta`,
unstable: `${prefix}-Beta`,
};
}
static get cacheDir() {
return stitchConfigDir.join('launcher');
}
/**
* Ensure that all official runtime feeds are available to the user
* by adding missing feeds to the appropriate GameMaker configs.
*/
static async ensureOfficialRuntimeFeeds(channels = ['lts', 'stable', 'beta']) {
const feedConfigs = [];
const runtimeUrls = runtimeFeedUrls();
for (const channel of channels) {
feedConfigs.push({
Key: channel,
Value: runtimeUrls[channel],
});
}
for (const configFile of await listRuntimeFeedsConfigPaths()) {
const existingConfig = await configFile.read({ fallback: [] });
maybeNew: for (const feed of feedConfigs) {
for (const existing of existingConfig) {
if (existing.Value === feed.Value) {
continue maybeNew;
}
}
// Then this is a new feed
existingConfig.push(feed);
}
await configFile.write(existingConfig);
}
}
}
__decorate([
MaxAge(60, 30),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], GameMakerComponent, "listWellKnownPaths", null);
//# sourceMappingURL=GameMakerComponent.js.map