From 23a06238983bf22aee6c68e365ab35acca2a8564 Mon Sep 17 00:00:00 2001 From: KNSO Date: Mon, 14 Sep 2026 13:41:18 +0200 Subject: [PATCH] =?UTF-8?q?fix(assets):=20Zielpfad=20der=20Aufbereitung=20?= =?UTF-8?q?unabh=C3=A4ngig=20vom=20Arbeitsverzeichnis?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit places.mjs schrieb bisher nach public/models/places/.glb relativ zu process.cwd() und nahm jede id ungeprüft an. Die neue, exportierte placeTarget(id) löst den Pfad stattdessen von import.meta.url ab (egal von wo das Skript aufgerufen wird) und lehnt IDs außerhalb von [a-z0-9-]+ ab, bevor daraus ein Pfad wird, der aus public/models/ places/ herausführen könnte. Co-Authored-By: Claude Sonnet 5 --- tests/placesPipeline.test.js | 21 ++++++++++++++++++++- tools/assets/places.mjs | 20 +++++++++++++++++--- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/placesPipeline.test.js b/tests/placesPipeline.test.js index 8dc8cec..6d78011 100644 --- a/tests/placesPipeline.test.js +++ b/tests/placesPipeline.test.js @@ -1,8 +1,9 @@ +import { isAbsolute } from "node:path"; import { beforeAll, describe, expect, it } from "vitest"; import { Document } from "@gltf-transform/core"; import sharp from "sharp"; import { EXT_MATERIALS_BUMP, EXTMaterialsBump } from "../tools/assets/bumpExtension.mjs"; -import { FAR_TEXTURE_SIZE, NEAR_TEXTURE_SIZE, optimizePlace } from "../tools/assets/places.mjs"; +import { FAR_TEXTURE_SIZE, NEAR_TEXTURE_SIZE, optimizePlace, placeTarget } from "../tools/assets/places.mjs"; /** 1100 px große Streifen. seed macht die Bilder verschieden, sonst legt dedup() sie zusammen. */ function stripes(seed) { @@ -95,3 +96,21 @@ describe("optimizePlace", () => { expect(used).not.toContain("EXT_mesh_gpu_instancing"); }); }); + +describe("placeTarget", () => { + // Bruch: ein Pfad relativ zum Arbeitsverzeichnis statt zum Projekt — bei einem Aufruf aus einem + // anderen Ordner (z. B. aus einem Skript) landete die Datei am falschen Ort oder mkdir schlug fehl. + it("liefert den absoluten Zielpfad unter public/models/places/", () => { + const target = placeTarget("hood-yard"); + + expect(isAbsolute(target)).toBe(true); + expect(target.endsWith("public/models/places/hood-yard.glb")).toBe(true); + }); + + // Bruch: eine ID mit Pfadtrennzeichen oder Großbuchstaben schreibt außerhalb von + // public/models/places/ oder unter einem Namen, den PLACES so nie referenziert. + it("lehnt IDs mit Pfadtrennzeichen oder Großbuchstaben ab", () => { + expect(() => placeTarget("../x")).toThrow(); + expect(() => placeTarget("Hood Yard")).toThrow(); + }); +}); diff --git a/tools/assets/places.mjs b/tools/assets/places.mjs index 1557111..1c8f008 100644 --- a/tools/assets/places.mjs +++ b/tools/assets/places.mjs @@ -8,7 +8,7 @@ // verwirft EXT_materials_bump, und darin steckt das Wellblech der Container (bumpExtension.mjs). import { mkdir, stat } from "node:fs/promises"; import { dirname } from "node:path"; -import { pathToFileURL } from "node:url"; +import { fileURLToPath, pathToFileURL } from "node:url"; import { ExtensionProperty, NodeIO, PropertyType } from "@gltf-transform/core"; import { ALL_EXTENSIONS, EXTTextureWebP } from "@gltf-transform/extensions"; import { compressTexture, dedup, flatten, getBounds, join, listTextureSlots, meshopt, prune, @@ -119,13 +119,27 @@ export function describePlace(document) { }; } +/** + * Zielpfad für ein aufbereitetes Modell, absolut und unabhängig vom Arbeitsverzeichnis (public/ + * models/places/.glb, relativ zu dieser Datei aufgelöst statt zu process.cwd()). id landet auch + * in src/world/places.js (PLACES) und im Dateinamen — nur Kleinbuchstaben, Ziffern und Bindestrich, + * damit sie auf jedem Dateisystem gleich aussieht und nicht aus public/models/places/ herausführt. + */ +export function placeTarget(id) { + if (!/^[a-z0-9-]+$/.test(id)) { + throw new Error(`Ungültige Orts-ID "${id}": erlaubt sind nur Kleinbuchstaben, Ziffern und Bindestrich.`); + } + const dir = new URL("../../public/models/places/", import.meta.url); + return fileURLToPath(new URL(`${id}.glb`, dir)); +} + async function main() { const [source, id] = process.argv.slice(2); if (!source || !id) { - console.error("Aufruf: node tools/assets/places.mjs "); + console.error("Aufruf: node tools/assets/places.mjs "); process.exit(1); } - const target = `public/models/places/${id}.glb`; + const target = placeTarget(id); const io = new NodeIO() .registerExtensions([...ALL_EXTENSIONS, EXTMaterialsBump]) .registerDependencies({ "meshopt.encoder": MeshoptEncoder });