Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement importing game metadata from annotated artifacts #57

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .yarn/patches/gray-matter-npm-4.0.3-852ae4f34c.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
diff --git a/lib/engines.js b/lib/engines.js
index 38f993db06cf364191ac635a6590c2d29303297d..1dfec8d9527653ad9ccfaacfa59732246fdb1e32 100644
--- a/lib/engines.js
+++ b/lib/engines.js
@@ -13,8 +13,8 @@ const engines = exports = module.exports;
*/

engines.yaml = {
- parse: yaml.safeLoad.bind(yaml),
- stringify: yaml.safeDump.bind(yaml)
+ parse: yaml.load.bind(yaml),
+ stringify: yaml.dump.bind(yaml)
};

/**
diff --git a/package.json b/package.json
index c5a1fff7587dd332d26865381eeaf76ef425eb80..2e3b6cb23f74bdca37535149a623bd8801300b94 100644
--- a/package.json
+++ b/package.json
@@ -34,7 +34,7 @@
"test": "mocha"
},
"dependencies": {
- "js-yaml": "^3.13.1",
+ "js-yaml": "^4.1.0",
"kind-of": "^6.0.2",
"section-matter": "^1.0.0",
"strip-bom-string": "^1.0.0"
12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,18 @@
"cheerio": "^1.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"dompurify": "^3.2.3",
"electron-devtools-installer": "^4.0.0",
"gray-matter": "patch:gray-matter@npm%3A4.0.3#~/.yarn/patches/gray-matter-npm-4.0.3-852ae4f34c.patch",
"immer": "^10.1.1",
"inversify": "^6.2.1",
"js-yaml": "^4.1.0",
"kysely": "^0.27.5",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0",
"reflect-metadata": "^0.2.2",
"remarkable": "^2.0.1",
"remeda": "^2.19.1",
"smol-toml": "^1.3.1",
"temporal-polyfill": "^0.2.5",
Expand All @@ -54,9 +59,11 @@
"@napi-rs/blake-hash": "^1.3.4",
"@types/better-sqlite3": "^7.6.12",
"@types/bunyan": "^1.8.11",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.17.14",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/remarkable": "^2.0.8",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.3.4",
"electron": "^33.3.1",
Expand All @@ -75,6 +82,7 @@
"vitest": "^3.0.1"
},
"resolutions": {
"@ibm/plex": "npm:[email protected]"
"@ibm/plex": "npm:[email protected]",
"gray-matter/js-yaml": "npm:4.1.0"
}
}
}
88 changes: 88 additions & 0 deletions src/libs/game-info/loader/DefaultGameInfoLoader.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { promises as fsa } from "node:fs";
import path from "node:path";

import { inject, injectable } from "inversify";

import {
BundledArtifactInfo,
BundledGameInfo,
BundledVersionInfo,
GameInfoParser,
} from "$game-info/parser";

import { BundledGameMetadata } from "./GameInfoLoader.mjs";

const META_FOLDER_NAME = ".meta";
const CHANGELOGS_FOLDER_NAME = "changelogs";
const GAME_MD_FILENAME = "game.md";

@injectable()
export class DefaultGameInfoLoader {
constructor(
@inject(GameInfoParser) private readonly gameInfoParser: GameInfoParser,
) {}

async loadGameInfoFromMetaFolder(
artifactFolder: string,
): Promise<BundledGameMetadata> {
const metaFolderPath = path.join(artifactFolder, META_FOLDER_NAME);
const gameMdPath = path.join(metaFolderPath, GAME_MD_FILENAME);
const changelogsDirPath = path.join(metaFolderPath, CHANGELOGS_FOLDER_NAME);

const [gameInfo, versionInfo] = await Promise.all([
this.loadBundledGameInfo(gameMdPath),
this.loadAllBundledVersionInfo(changelogsDirPath),
]);
return { gameInfo, versionInfo };
}

async loadBundledGameInfo(gameMdPath: string): Promise<BundledGameInfo> {
const gameMD = await fsa.readFile(gameMdPath, "utf-8");
return this.gameInfoParser.parseGameInfo(gameMD);
}

async loadBundledArtifactInfo(
artifactYamlPath: string,
): Promise<BundledArtifactInfo> {
const artifactYaml = await fsa.readFile(artifactYamlPath, "utf-8");
return this.gameInfoParser.parseArtifactYaml(artifactYaml);
}

async loadAllBundledVersionInfo(
changelogsDirPath: string,
): Promise<BundledVersionInfo[]> {
const changelogFiles = await fsa.readdir(changelogsDirPath, {
withFileTypes: true,
recursive: false,
});
const versionInfos = await Promise.all(
changelogFiles.map(async (changelogDirent) => {
if (!changelogDirent.isFile()) {
return null;
}
const changelogFilename = changelogDirent.name;
if (
changelogFilename.length < 4 || // require at least one version character
changelogFilename.slice(-3).toLowerCase() !== ".md"
) {
return null;
}

const version = changelogFilename.slice(0, -3);
try {
return await this.loadBundledVersionInfo(
version,
path.join(changelogsDirPath, changelogFilename),
);
} catch {
return null;
}
}),
);
return versionInfos.filter((v) => v != null);
}
async loadBundledVersionInfo(version: string, changelogMdPath: string) {
const changelogMD = await fsa.readFile(changelogMdPath, "utf-8");
return this.gameInfoParser.parseChangelog(version, changelogMD);
}
}
89 changes: 89 additions & 0 deletions src/libs/game-info/loader/DefaultGameInfoLoader.spec.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as path from "node:path";

import { Ajv } from "ajv/dist/jtd";
import { beforeEach, describe, expect, it } from "vitest";

import { DefaultGameInfoParser } from "../parser/DefaultGameInfoParser.mjs";
import { DefaultGameInfoLoader } from "./DefaultGameInfoLoader.mjs";

describe("DefaultGameInfoLoader", () => {
const ajv = new Ajv();
let gameInfoParser: DefaultGameInfoParser;

beforeEach(() => {
gameInfoParser = new DefaultGameInfoParser(ajv);
});

it("should be instantiatable", () => {
const subject = new DefaultGameInfoLoader(gameInfoParser);
expect(subject).toBeInstanceOf(DefaultGameInfoLoader);
});

describe("instance", () => {
let subject: DefaultGameInfoLoader;

beforeEach(() => {
subject = new DefaultGameInfoLoader(gameInfoParser);
});

it("should load game info from the meta folder", async () => {
const gameMdPath = path.join(import.meta.dirname, "test/meta-0/game.md");

const gameInfo = subject.loadBundledGameInfo(gameMdPath);
await expect(gameInfo).resolves.matchSnapshot();
});
it("should throw an error when loading game info from a non-existent path", async () => {
const gameMdPath = path.join(
import.meta.dirname,
"test/meta-0/does-not-exist.md",
);

const gameInfo = subject.loadBundledGameInfo(gameMdPath);
await expect(gameInfo).rejects.toMatchObject({
code: "ENOENT",
});
});

it("should load artifact info from the meta folder", async () => {
const artifactYamlPath = path.join(
import.meta.dirname,
"test/meta-0/artifact.yaml",
);

const artifactInfo = subject.loadBundledArtifactInfo(artifactYamlPath);
await expect(artifactInfo).resolves.matchSnapshot();
});
it("should throw an error when loading artifact info from a non-existent path", async () => {
const artifactYamlPath = path.join(
import.meta.dirname,
"test/meta-0/does-not-exist.yaml",
);

const artifactInfo = subject.loadBundledArtifactInfo(artifactYamlPath);
await expect(artifactInfo).rejects.toMatchObject({
code: "ENOENT",
});
});

it("should load all bundled version info from the changelogs folder", async () => {
const changelogsDirPath = path.join(
import.meta.dirname,
"test/meta-0/changelogs",
);

const versionInfos = subject.loadAllBundledVersionInfo(changelogsDirPath);
await expect(versionInfos).resolves.matchSnapshot();
});
it("should throw an error when loading version info from a non-existent path", async () => {
const changelogsDirPath = path.join(
import.meta.dirname,
"test/meta-0/does-not-exist",
);

const versionInfos = subject.loadAllBundledVersionInfo(changelogsDirPath);
await expect(versionInfos).rejects.toMatchObject({
code: "ENOENT",
});
});
});
});
17 changes: 17 additions & 0 deletions src/libs/game-info/loader/GameInfoLoader.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BundledGameInfo, BundledVersionInfo } from "$game-info/parser";
import { makeServiceIdentifier } from "$node-base/utils";

const GameInfoLoader =
makeServiceIdentifier<GameInfoLoader>("game info loader");
interface GameInfoLoader {
loadGameInfoFromMetaFolder(
metaFolderPath: string,
): Promise<BundledGameMetadata>;
}

export { GameInfoLoader };

export interface BundledGameMetadata {
gameInfo: BundledGameInfo;
versionInfo: BundledVersionInfo[];
}
Loading
Loading