fix: mirror manual furni uploads to live assets
This commit is contained in:
1 parent
01570874a8
commit
86cd43def9
2 files changed
+193
-2
No files matched your search
@@ -0,0 +1,139 @@
|
|||||||
|
// @vitest-environment node
|
||||||
|
import { promises as fs } from "node:fs";
|
||||||
|
import os from "node:os";
|
||||||
|
import path from "node:path";
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const {
|
||||||
|
appendFurniEntryMock,
|
||||||
|
assetTargets,
|
||||||
|
dbExecuteMock,
|
||||||
|
getOrCreateCategoryPageMock,
|
||||||
|
} = vi.hoisted(() => ({
|
||||||
|
appendFurniEntryMock: vi.fn(),
|
||||||
|
assetTargets: {
|
||||||
|
swfDir: "",
|
||||||
|
iconDir: "",
|
||||||
|
nitroDir: "",
|
||||||
|
mirrorDirs: [] as Array<{
|
||||||
|
swfDir: string;
|
||||||
|
iconDir: string;
|
||||||
|
nitroDir: string;
|
||||||
|
}>,
|
||||||
|
},
|
||||||
|
dbExecuteMock: vi.fn(),
|
||||||
|
getOrCreateCategoryPageMock: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("drizzle-orm", () => ({
|
||||||
|
eq: vi.fn(() => ({})),
|
||||||
|
sql: (strings: TemplateStringsArray, ...values: unknown[]) => ({
|
||||||
|
strings,
|
||||||
|
values,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/db", () => ({
|
||||||
|
ItemsBase: { id: "id", itemName: "item_name" },
|
||||||
|
db: {
|
||||||
|
execute: dbExecuteMock,
|
||||||
|
select: () => ({
|
||||||
|
from: () => ({
|
||||||
|
where: () => ({
|
||||||
|
limit: async () => [],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/services/furni-asset-dirs", () => ({
|
||||||
|
getFurniAssetDirs: vi.fn(async () => assetTargets),
|
||||||
|
getFurniAssetWriteTargets: vi.fn(async () => assetTargets),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/services/furni-data", () => ({
|
||||||
|
appendFurniEntry: appendFurniEntryMock,
|
||||||
|
buildFurniEntry: vi.fn(() => ({ id: 501 })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/services/furni-import", () => ({
|
||||||
|
allocateCatalogItemId: vi.fn(
|
||||||
|
async (insertFn: (nextId: number) => Promise<unknown>) => insertFn(701),
|
||||||
|
),
|
||||||
|
autoPriceFurni: vi.fn(() => ({ credits: 3, points: 0, pointsType: 0 })),
|
||||||
|
ensureDirectories: vi.fn(async () => {
|
||||||
|
const dirs = [
|
||||||
|
assetTargets.swfDir,
|
||||||
|
assetTargets.iconDir,
|
||||||
|
assetTargets.nitroDir,
|
||||||
|
...assetTargets.mirrorDirs.flatMap((target) => [
|
||||||
|
target.swfDir,
|
||||||
|
target.iconDir,
|
||||||
|
target.nitroDir,
|
||||||
|
]),
|
||||||
|
];
|
||||||
|
await Promise.all(dirs.map((dir) => fs.mkdir(dir, { recursive: true })));
|
||||||
|
}),
|
||||||
|
getOrCreateCategoryPage: getOrCreateCategoryPageMock,
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("@/lib/services/swf/nitro-builder", () => ({
|
||||||
|
parseNitroBundle: vi.fn(() => ({ json: { xdim: 1, ydim: 1 } })),
|
||||||
|
}));
|
||||||
|
|
||||||
|
import { uploadSingleFurni } from "./upload-import";
|
||||||
|
|
||||||
|
describe("uploadSingleFurni live asset mirrors", () => {
|
||||||
|
let tempDir: string;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "upload-furni-"));
|
||||||
|
assetTargets.swfDir = path.join(tempDir, "cms", "swf");
|
||||||
|
assetTargets.iconDir = path.join(tempDir, "cms", "icons");
|
||||||
|
assetTargets.nitroDir = path.join(tempDir, "cms", "furniture");
|
||||||
|
assetTargets.mirrorDirs = [
|
||||||
|
{
|
||||||
|
swfDir: assetTargets.swfDir,
|
||||||
|
iconDir: path.join(tempDir, "Gamedata", "icons"),
|
||||||
|
nitroDir: path.join(tempDir, "Gamedata", "bundled", "furniture"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
dbExecuteMock.mockReset();
|
||||||
|
dbExecuteMock
|
||||||
|
.mockResolvedValueOnce([[{ next: 501 }], undefined])
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
getOrCreateCategoryPageMock.mockResolvedValue("imp_other");
|
||||||
|
appendFurniEntryMock.mockResolvedValue(undefined);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await fs.rm(tempDir, { recursive: true, force: true });
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("copies the Nitro bundle and icon into the directories served by production", async () => {
|
||||||
|
const nitroBuffer = Buffer.from("nitro-bundle");
|
||||||
|
const iconBuffer = Buffer.from("png-icon");
|
||||||
|
|
||||||
|
const result = await uploadSingleFurni({
|
||||||
|
classname: "chair",
|
||||||
|
name: "Chair",
|
||||||
|
itemType: "s",
|
||||||
|
nitroBuffer,
|
||||||
|
iconBuffer,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.ok).toBe(true);
|
||||||
|
await expect(
|
||||||
|
fs.readFile(
|
||||||
|
path.join(assetTargets.mirrorDirs[0].nitroDir, "chair.nitro"),
|
||||||
|
),
|
||||||
|
).resolves.toEqual(nitroBuffer);
|
||||||
|
await expect(
|
||||||
|
fs.readFile(
|
||||||
|
path.join(assetTargets.mirrorDirs[0].iconDir, "chair_icon.png"),
|
||||||
|
),
|
||||||
|
).resolves.toEqual(iconBuffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import { existsSync, promises as fs } from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { eq, sql } from "drizzle-orm";
|
import { eq, sql } from "drizzle-orm";
|
||||||
import { db, ItemsBase } from "@/lib/db";
|
import { db, ItemsBase } from "@/lib/db";
|
||||||
import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
|
import { getFurniAssetWriteTargets } from "@/lib/services/furni-asset-dirs";
|
||||||
import { appendFurniEntry, buildFurniEntry } from "@/lib/services/furni-data";
|
import { appendFurniEntry, buildFurniEntry } from "@/lib/services/furni-data";
|
||||||
import {
|
import {
|
||||||
allocateCatalogItemId,
|
allocateCatalogItemId,
|
||||||
@@ -43,6 +43,36 @@ const MIGRATIONS_DIR = path.resolve(
|
|||||||
|
|
||||||
let itemsBaseIdAllocChain: Promise<unknown> = Promise.resolve();
|
let itemsBaseIdAllocChain: Promise<unknown> = Promise.resolve();
|
||||||
|
|
||||||
|
function pathKey(value: string): string {
|
||||||
|
const normalized = path.normalize(value);
|
||||||
|
return process.platform === "win32" ? normalized.toLowerCase() : normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mirrorUploadedAsset(
|
||||||
|
sourcePath: string,
|
||||||
|
fileName: string,
|
||||||
|
dirs: string[],
|
||||||
|
warnings: string[],
|
||||||
|
copiedPaths: string[],
|
||||||
|
): Promise<void> {
|
||||||
|
const sourceKey = pathKey(sourcePath);
|
||||||
|
const seen = new Set<string>();
|
||||||
|
for (const dir of dirs) {
|
||||||
|
const targetPath = path.join(/*turbopackIgnore: true*/ dir, fileName);
|
||||||
|
const targetKey = pathKey(targetPath);
|
||||||
|
if (targetKey === sourceKey || seen.has(targetKey)) continue;
|
||||||
|
seen.add(targetKey);
|
||||||
|
try {
|
||||||
|
await fs.copyFile(sourcePath, targetPath);
|
||||||
|
copiedPaths.push(targetPath);
|
||||||
|
} catch (err) {
|
||||||
|
warnings.push(
|
||||||
|
`asset mirror failed (${targetPath}): ${(err as Error).message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function allocateItemsBaseId<T>(
|
async function allocateItemsBaseId<T>(
|
||||||
insertFn: (nextId: number) => Promise<T>,
|
insertFn: (nextId: number) => Promise<T>,
|
||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
@@ -200,7 +230,8 @@ export async function uploadSingleFurni(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ensureDirectories();
|
await ensureDirectories();
|
||||||
const { iconDir, nitroDir } = await getFurniAssetDirs();
|
const assetTargets = await getFurniAssetWriteTargets();
|
||||||
|
const { iconDir, nitroDir } = assetTargets;
|
||||||
|
|
||||||
const nitroFileName = `${classname}.nitro`;
|
const nitroFileName = `${classname}.nitro`;
|
||||||
const nitroPath = path.join(
|
const nitroPath = path.join(
|
||||||
@@ -225,6 +256,24 @@ export async function uploadSingleFurni(params: {
|
|||||||
await fs.writeFile(iconPath, iconBuffer);
|
await fs.writeFile(iconPath, iconBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mirroredPaths: string[] = [];
|
||||||
|
await mirrorUploadedAsset(
|
||||||
|
nitroPath,
|
||||||
|
nitroFileName,
|
||||||
|
assetTargets.mirrorDirs.map((target) => target.nitroDir),
|
||||||
|
warnings,
|
||||||
|
mirroredPaths,
|
||||||
|
);
|
||||||
|
if (iconPath && iconFileName) {
|
||||||
|
await mirrorUploadedAsset(
|
||||||
|
iconPath,
|
||||||
|
iconFileName,
|
||||||
|
assetTargets.mirrorDirs.map((target) => target.iconDir),
|
||||||
|
warnings,
|
||||||
|
mirroredPaths,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const xdim = userXdim ?? Number(nitroMeta?.xdim ?? 1);
|
const xdim = userXdim ?? Number(nitroMeta?.xdim ?? 1);
|
||||||
const ydim = userYdim ?? Number(nitroMeta?.ydim ?? 1);
|
const ydim = userYdim ?? Number(nitroMeta?.ydim ?? 1);
|
||||||
|
|
||||||
@@ -248,6 +297,9 @@ export async function uploadSingleFurni(params: {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
await fs.unlink(nitroPath).catch(() => {});
|
await fs.unlink(nitroPath).catch(() => {});
|
||||||
if (iconPath) await fs.unlink(iconPath).catch(() => {});
|
if (iconPath) await fs.unlink(iconPath).catch(() => {});
|
||||||
|
await Promise.all(
|
||||||
|
mirroredPaths.map((file) => fs.unlink(file).catch(() => {})),
|
||||||
|
);
|
||||||
return {
|
return {
|
||||||
ok: false,
|
ok: false,
|
||||||
classname,
|
classname,
|
||||||
|
|||||||
Reference in new issue
Block a user