Release Healer Man 0.1.22
This commit is contained in:
1 parent
f6f2375145
commit
7f6001723a
144 files changed
+54107
-690
No files matched your search
@@ -1,12 +1,15 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import { access, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import test from "node:test";
|
||||
import {
|
||||
BUNDLED_ANDROID_PACKS,
|
||||
PROJECT_ROOT,
|
||||
assertCampaignDungeonManifest,
|
||||
contentPackForPath,
|
||||
createContentManifest,
|
||||
loadCampaignDungeonPackDefinitions,
|
||||
publishContent,
|
||||
scanContentSources,
|
||||
} from "./publish-content.mjs";
|
||||
@@ -31,6 +34,160 @@ test("assigns stable player-facing packs from logical asset paths", () => {
|
||||
assert.equal(contentPackForPath("basis/basis_transcoder.wasm"), "core:runtime");
|
||||
});
|
||||
|
||||
test("gives every WoW campaign dungeon an exact install dependency closure", async () => {
|
||||
const campaign = JSON.parse(await readFile(path.join(
|
||||
PROJECT_ROOT,
|
||||
"src",
|
||||
"game",
|
||||
"generated",
|
||||
"dungeonCampaignCatalog.json",
|
||||
), "utf8"));
|
||||
const packDefinitions = await loadCampaignDungeonPackDefinitions();
|
||||
const dungeonDefinitions = packDefinitions.filter((pack) => pack.id.startsWith("dungeon:"));
|
||||
const wailingCreatureManifest = JSON.parse(await readFile(path.join(
|
||||
PROJECT_ROOT,
|
||||
"public",
|
||||
"assets",
|
||||
"creatures",
|
||||
"wailing-caverns",
|
||||
"animated-manifest.json",
|
||||
), "utf8"));
|
||||
assert.equal(campaign.definitions.length, 54);
|
||||
assert.equal(dungeonDefinitions.length, 54);
|
||||
assert.equal(wailingCreatureManifest.creatures.length, 16);
|
||||
|
||||
const referencedSourceFiles = [
|
||||
...campaign.definitions.flatMap((dungeon) => [
|
||||
...[
|
||||
...dungeon.assets.visual,
|
||||
...dungeon.assets.collision,
|
||||
dungeon.assets.navigation,
|
||||
].map((asset) => ({ dungeonId: dungeon.id, url: asset.url })),
|
||||
...Object.values(dungeon.entities).flatMap((entity) => (
|
||||
entity.visual?.model?.url ? [{ dungeonId: dungeon.id, url: entity.visual.model.url }] : []
|
||||
)),
|
||||
]),
|
||||
...wailingCreatureManifest.creatures.map((creature) => ({
|
||||
dungeonId: "wailing-caverns",
|
||||
url: creature.url,
|
||||
})),
|
||||
];
|
||||
await Promise.all(referencedSourceFiles.map(async ({ dungeonId, url }) => {
|
||||
try {
|
||||
await access(path.join(PROJECT_ROOT, "public", ...url.slice(1).split("/")));
|
||||
} catch {
|
||||
assert.fail(`${dungeonId} is missing ${url}`);
|
||||
}
|
||||
}));
|
||||
await Promise.all([
|
||||
"wailing-caverns-visual.glb",
|
||||
"wailing-caverns-collision.glb",
|
||||
"wailing-caverns-navigation.glb",
|
||||
].map((fileName) => access(path.join(
|
||||
PROJECT_ROOT,
|
||||
"src",
|
||||
"assets",
|
||||
"game",
|
||||
"dungeons",
|
||||
"wailing-caverns",
|
||||
fileName,
|
||||
))));
|
||||
|
||||
const logicalFiles = new Map();
|
||||
const addFile = (logicalPath) => logicalFiles.set(logicalPath, {
|
||||
logicalPath,
|
||||
sourcePath: logicalPath,
|
||||
sha256: "a".repeat(64),
|
||||
bytes: 1,
|
||||
extension: path.extname(logicalPath),
|
||||
packId: contentPackForPath(logicalPath),
|
||||
});
|
||||
addFile("assets/game/dungeons/wailing-caverns/visual.glb");
|
||||
for (const dungeon of campaign.definitions) {
|
||||
for (const asset of [
|
||||
...dungeon.assets.visual,
|
||||
...dungeon.assets.collision,
|
||||
dungeon.assets.navigation,
|
||||
]) addFile(asset.url.slice(1));
|
||||
for (const entity of Object.values(dungeon.entities)) {
|
||||
if (entity.visual?.model?.url) addFile(entity.visual.model.url.slice(1));
|
||||
}
|
||||
}
|
||||
const manifest = createContentManifest([...logicalFiles.values()], {
|
||||
packDefinitions,
|
||||
version: "campaign-contract",
|
||||
});
|
||||
assert.doesNotThrow(() => assertCampaignDungeonManifest(manifest, campaign.definitions));
|
||||
const packs = new Map(manifest.packs.map((pack) => [pack.id, pack]));
|
||||
|
||||
for (const dungeon of campaign.definitions) {
|
||||
const pack = packs.get(`dungeon:${dungeon.id}`);
|
||||
assert.ok(pack, dungeon.id);
|
||||
if (dungeon.id === "wailing-caverns") {
|
||||
assert.equal(pack.bundledOnAndroid, true);
|
||||
assert.deepEqual(pack.dependencies, []);
|
||||
continue;
|
||||
}
|
||||
const environmentPack = contentPackForPath(dungeon.assets.visual[0].url.slice(1));
|
||||
const needsSharedCreatures = Object.values(dungeon.entities).some((entity) => (
|
||||
entity.visual?.model?.url?.startsWith("/assets/game/creatures/shared/")
|
||||
));
|
||||
assert.deepEqual(
|
||||
pack.dependencies,
|
||||
[environmentPack, ...(needsSharedCreatures ? ["shared:creatures"] : [])],
|
||||
dungeon.id,
|
||||
);
|
||||
assert.deepEqual(packs.get(environmentPack)?.dependencies, [], dungeon.id);
|
||||
}
|
||||
|
||||
for (const [dungeonId, environmentPack] of [
|
||||
["tinkertech-showdown", "manastorm:821-tinketechshowdown"],
|
||||
["frozen-reach", "manastorm:908-alvaencounter"],
|
||||
["forgotten-mine", "manastorm:1781-forgottenmine"],
|
||||
]) {
|
||||
const pack = packs.get(`dungeon:${dungeonId}`);
|
||||
assert.equal(pack.fileCount, 0);
|
||||
assert.equal(pack.bytes, 0);
|
||||
assert.deepEqual(pack.dependencies, [environmentPack]);
|
||||
}
|
||||
});
|
||||
|
||||
test("default release publishing fails before writing an incomplete campaign manifest", async () => {
|
||||
const directory = await mkdtemp(path.join(os.tmpdir(), "healer-man-incomplete-content-test-"));
|
||||
const sourceDirectory = path.join(directory, "source");
|
||||
const contentDirectory = path.join(directory, "content");
|
||||
await mkdir(sourceDirectory, { recursive: true });
|
||||
await writeFile(path.join(sourceDirectory, "placeholder.glb"), Buffer.from("incomplete"));
|
||||
|
||||
try {
|
||||
await assert.rejects(publishContent({
|
||||
contentDirectory,
|
||||
sources: [{ directory: sourceDirectory, logicalPrefix: "assets/game/dungeons/placeholder" }],
|
||||
}), /Release content manifest is missing/);
|
||||
await assert.rejects(access(contentDirectory), { code: "ENOENT" });
|
||||
} finally {
|
||||
await rm(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("content versions change when only logical pack dependencies change", () => {
|
||||
const files = [{
|
||||
logicalPath: "assets/game/dungeons/test/creature.glb",
|
||||
sourcePath: "creature.glb",
|
||||
sha256: "b".repeat(64),
|
||||
bytes: 1,
|
||||
extension: ".glb",
|
||||
packId: "dungeon:test",
|
||||
}];
|
||||
const first = createContentManifest(files, {
|
||||
packDefinitions: [{ id: "dungeon:test", dependencies: ["manastorm:first"] }],
|
||||
});
|
||||
const second = createContentManifest(files, {
|
||||
packDefinitions: [{ id: "dungeon:test", dependencies: ["manastorm:second"] }],
|
||||
});
|
||||
assert.notEqual(first.version, second.version);
|
||||
});
|
||||
|
||||
test("publishes objects before a deterministic latest manifest", async () => {
|
||||
const directory = await mkdtemp(path.join(os.tmpdir(), "healer-man-content-test-"));
|
||||
const sourceDirectory = path.join(directory, "source");
|
||||
@@ -45,7 +202,12 @@ test("publishes objects before a deterministic latest manifest", async () => {
|
||||
assert.equal(Object.keys(firstManifest.files).length, 1);
|
||||
assert.equal(firstManifest.packs[0].id, "dungeon:wailing-caverns");
|
||||
|
||||
const first = await publishContent({ contentDirectory, sources, createdAt: "2026-08-14T00:00:00.000Z" });
|
||||
const first = await publishContent({
|
||||
contentDirectory,
|
||||
sources,
|
||||
includeCampaignPacks: false,
|
||||
createdAt: "2026-08-14T00:00:00.000Z",
|
||||
});
|
||||
assert.equal(first.copiedObjects, 1);
|
||||
const latest = JSON.parse(await readFile(path.join(contentDirectory, "manifest.json"), "utf8"));
|
||||
assert.equal(latest.version, first.manifest.version);
|
||||
@@ -53,7 +215,12 @@ test("publishes objects before a deterministic latest manifest", async () => {
|
||||
assert.match(entry.url, /^\/content\/objects\/[a-f0-9]{64}\.glb$/);
|
||||
assert.equal(await readFile(path.join(contentDirectory, "objects", path.basename(entry.url)), "utf8"), "glb-one");
|
||||
|
||||
const second = await publishContent({ contentDirectory, sources, createdAt: "2026-08-14T00:00:00.000Z" });
|
||||
const second = await publishContent({
|
||||
contentDirectory,
|
||||
sources,
|
||||
includeCampaignPacks: false,
|
||||
createdAt: "2026-08-14T00:00:00.000Z",
|
||||
});
|
||||
assert.equal(second.copiedObjects, 0);
|
||||
assert.equal(second.manifest.version, first.manifest.version);
|
||||
} finally {
|
||||
|
||||
Reference in new issue
Block a user