From 4a0fb1bfae87c82b6d9a3f9d28839187ce63d342 Mon Sep 17 00:00:00 2001 From: suyx Date: Tue, 4 Aug 2026 18:22:28 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(WP4-07):=20=E6=94=AF=E6=8C=81=E6=96=B0?= =?UTF-8?q?=E7=9A=84=E8=B4=B4=E7=BA=B8=E8=B5=84=E6=BA=90=E6=A0=B9=E7=9B=AE?= =?UTF-8?q?=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/compile-wp5-03-assets.mjs | 29 +++++++++++++++---- .../visual-performance/wp4-07-real-assets.mjs | 9 +++--- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/scripts/compile-wp5-03-assets.mjs b/scripts/compile-wp5-03-assets.mjs index 777e668..903da3f 100644 --- a/scripts/compile-wp5-03-assets.mjs +++ b/scripts/compile-wp5-03-assets.mjs @@ -1,7 +1,7 @@ import { createHash } from "node:crypto"; -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; -import { join, resolve } from "node:path"; +import { dirname, join, resolve } from "node:path"; import { compileAssetArchive, compileStaticStickerCatalog } from "../packages/asset-compiler/dist/index.js"; import { createP0aColorCardRenderPlans } from "../packages/asset-renderer/dist/index.js"; @@ -18,8 +18,9 @@ import { const runDirectory = resolve(process.env.DADA_WP5_03_RUN_DIRECTORY ?? "artifacts/tdd/wp5-03-local"); const whiteDirectory = resolve(process.env.DADA_WP5_03_WHITE_EVIDENCE_DIR ?? join(runDirectory, "cases", "TDD-WP5-WHITE-001-p0a-allowlist")); const colorDirectory = resolve(process.env.DADA_WP5_03_COLOR_EVIDENCE_DIR ?? join(runDirectory, "cases", "TDD-WP5-COL-001-four-layouts")); -const handoffManifest = resolve(process.env.DADA_COMPLEX_ASSET_MANIFEST ?? join(homedir(), "Desktop", "sticker_web_handoff", "sticker_web_catalog_manifest.json")); -const stickerRoot = resolve(process.env.DADA_STATIC_STICKER_ROOT ?? join(homedir(), "Desktop", "贴纸素材")); +const replicationRoot = resolve(process.env.DADA_REPLICATION_ASSET_ROOT ?? join(homedir(), "Desktop", "sticker_web_replication_assets")); +const handoffManifest = resolve(process.env.DADA_COMPLEX_ASSET_MANIFEST ?? join(replicationRoot, "sticker_web_handoff", "sticker_web_catalog_manifest.json")); +const stickerRoot = resolve(process.env.DADA_STATIC_STICKER_ROOT ?? join(replicationRoot, "sticker_normal")); if (!existsSync(handoffManifest)) throw new Error("normalized complex asset handoff is unavailable"); if (!existsSync(stickerRoot)) throw new Error("static sticker source is unavailable"); @@ -28,8 +29,26 @@ const staticDirectory = resolve(runDirectory, "inputs", "static"); mkdirSync(whiteDirectory, { recursive: true }); mkdirSync(colorDirectory, { recursive: true }); +const sourceHandoff = JSON.parse(readFileSync(handoffManifest, "utf8")); +const normalizedHandoff = { + ...sourceHandoff, + web_handoff: "STICKER_WEB_REPLICATION_HANDOFF.md", + validation: "sticker_archive_validation_20260722.json", + collections: sourceHandoff.collections + .filter((collection) => collection.id !== "normal_stickers") + .map((collection) => ({ + ...collection, + root: resolve(dirname(handoffManifest), collection.root), + })), +}; +const normalizedHandoffPath = resolve(complexDirectory, "sticker_web_catalog_manifest.normalized.json"); +mkdirSync(complexDirectory, { recursive: true }); +copyFileSync(resolve(dirname(handoffManifest), sourceHandoff.web_handoff), resolve(complexDirectory, normalizedHandoff.web_handoff)); +copyFileSync(resolve(dirname(handoffManifest), sourceHandoff.validation), resolve(complexDirectory, normalizedHandoff.validation)); +writeFileSync(normalizedHandoffPath, `${JSON.stringify(normalizedHandoff, null, 2)}\n`); + const complex = compileAssetArchive({ - manifestPath: handoffManifest, + manifestPath: normalizedHandoffPath, outputDirectory: complexDirectory, releaseVersion: P0A_COMPLEX_RELEASE_VERSION, }); diff --git a/tests/visual-performance/wp4-07-real-assets.mjs b/tests/visual-performance/wp4-07-real-assets.mjs index ed9dd10..da46566 100644 --- a/tests/visual-performance/wp4-07-real-assets.mjs +++ b/tests/visual-performance/wp4-07-real-assets.mjs @@ -1,7 +1,7 @@ import { createHash } from "node:crypto"; import { existsSync, readFileSync, readdirSync, statSync } from "node:fs"; import { homedir } from "node:os"; -import { basename, extname, isAbsolute, join, relative, resolve, sep } from "node:path"; +import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path"; import { WP4_07_REAL_RESOURCE_VERSIONS } from "./wp4-07-fixture.mjs"; @@ -74,15 +74,16 @@ function assetRecord(assetId, path, sourceReference, expectedSha256) { export function loadWp407RealAssets() { const manifestPath = resolve(process.env.DADA_WP4_07_FINAL_ASSET_MANIFEST ?? ""); if (!process.env.DADA_WP4_07_FINAL_ASSET_MANIFEST || !existsSync(manifestPath)) throw new Error("WP4_07_FINAL_ASSET_MANIFEST_REQUIRED"); - const handoffPath = resolve(process.env.DADA_COMPLEX_ASSET_MANIFEST ?? join(homedir(), "Desktop", "sticker_web_handoff", "sticker_web_catalog_manifest.json")); - const staticRoot = resolve(process.env.DADA_STATIC_STICKER_ROOT ?? join(homedir(), "Desktop", "贴纸素材")); + const replicationRoot = resolve(process.env.DADA_REPLICATION_ASSET_ROOT ?? join(homedir(), "Desktop", "sticker_web_replication_assets")); + const handoffPath = resolve(process.env.DADA_COMPLEX_ASSET_MANIFEST ?? join(replicationRoot, "sticker_web_handoff", "sticker_web_catalog_manifest.json")); + const staticRoot = resolve(process.env.DADA_STATIC_STICKER_ROOT ?? join(replicationRoot, "sticker_normal")); const backgroundPath = resolve(process.env.DADA_WP4_07_BACKGROUND_PATH ?? join(homedir(), "Documents", "贴纸脚本", "time_01_input_20260716.png")); if (!existsSync(handoffPath) || !existsSync(staticRoot)) throw new Error("WP4_07_REAL_ARCHIVE_ROOT_REQUIRED"); const manifestRaw = readFileSync(manifestPath, "utf8"); const manifest = JSON.parse(manifestRaw); const handoff = JSON.parse(readFileSync(handoffPath, "utf8")); - const collectionRoots = Object.fromEntries(handoff.collections.map((collection) => [collection.id, resolve(collection.root)])); + const collectionRoots = Object.fromEntries(handoff.collections.map((collection) => [collection.id, resolve(dirname(handoffPath), collection.root)])); const fontRoot = collectionRoots.font_panel; const dynamicRoot = collectionRoots.interactive_stickers; if (!fontRoot || !dynamicRoot) throw new Error("WP4_07_REAL_ARCHIVE_COLLECTION_REQUIRED"); From f4fabb66e5bf247078fd05d90407275cd39920dd Mon Sep 17 00:00:00 2001 From: suyx Date: Tue, 4 Aug 2026 18:29:17 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(WP4-07):=20=E5=85=BC=E5=AE=B9=E8=BF=81?= =?UTF-8?q?=E7=A7=BB=E5=90=8E=E7=9A=84=E5=AD=97=E4=BD=93=E8=B5=84=E6=BA=90?= =?UTF-8?q?=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/asset-compiler/src/index.ts | 8 +++++++- scripts/compile-wp5-03-assets.mjs | 9 +++++---- tests/unit/wp5-01-asset-compiler.test.ts | 21 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/packages/asset-compiler/src/index.ts b/packages/asset-compiler/src/index.ts index a8feb57..da61498 100644 --- a/packages/asset-compiler/src/index.ts +++ b/packages/asset-compiler/src/index.ts @@ -228,7 +228,13 @@ function readCsv(tracker: SourceTracker, path: string, label: string): CsvRow[] function itemDirectoryFor(collection: CollectionConfig, catalogPath: string, row: CsvRow): { directory: string; metadataPath: string } { if (collection.id === "font_panel") { - const resourceDir = requireString(row.resource_dir, "font resource_dir"); + const configuredResourceDir = requireString(row.resource_dir, "font resource_dir"); + const normalizedResourceDir = configuredResourceDir.replaceAll("\\", "/"); + const relocationMarker = "/resources/font_packages/"; + const markerIndex = normalizedResourceDir.lastIndexOf(relocationMarker); + const resourceDir = isAbsolute(configuredResourceDir) && !inside(configuredResourceDir, collection.root.path) && markerIndex >= 0 + ? relativeReference(normalizedResourceDir.slice(markerIndex + 1), "font resource_dir relocation") + : configuredResourceDir; const directory = resolveSourcePath(resourceDir, collection.root.path, collection.root.path, "font resource_dir"); return { directory, metadataPath: resolveSourcePath("metadata.json", directory, collection.root.path, "font metadata") }; } diff --git a/scripts/compile-wp5-03-assets.mjs b/scripts/compile-wp5-03-assets.mjs index 903da3f..a2d33dd 100644 --- a/scripts/compile-wp5-03-assets.mjs +++ b/scripts/compile-wp5-03-assets.mjs @@ -26,6 +26,7 @@ if (!existsSync(stickerRoot)) throw new Error("static sticker source is unavaila const complexDirectory = resolve(runDirectory, "inputs", "complex"); const staticDirectory = resolve(runDirectory, "inputs", "static"); +const normalizedHandoffDirectory = resolve(runDirectory, "inputs", "normalized-handoff"); mkdirSync(whiteDirectory, { recursive: true }); mkdirSync(colorDirectory, { recursive: true }); @@ -41,10 +42,10 @@ const normalizedHandoff = { root: resolve(dirname(handoffManifest), collection.root), })), }; -const normalizedHandoffPath = resolve(complexDirectory, "sticker_web_catalog_manifest.normalized.json"); -mkdirSync(complexDirectory, { recursive: true }); -copyFileSync(resolve(dirname(handoffManifest), sourceHandoff.web_handoff), resolve(complexDirectory, normalizedHandoff.web_handoff)); -copyFileSync(resolve(dirname(handoffManifest), sourceHandoff.validation), resolve(complexDirectory, normalizedHandoff.validation)); +const normalizedHandoffPath = resolve(normalizedHandoffDirectory, "sticker_web_catalog_manifest.normalized.json"); +mkdirSync(normalizedHandoffDirectory, { recursive: true }); +copyFileSync(resolve(dirname(handoffManifest), sourceHandoff.web_handoff), resolve(normalizedHandoffDirectory, normalizedHandoff.web_handoff)); +copyFileSync(resolve(dirname(handoffManifest), sourceHandoff.validation), resolve(normalizedHandoffDirectory, normalizedHandoff.validation)); writeFileSync(normalizedHandoffPath, `${JSON.stringify(normalizedHandoff, null, 2)}\n`); const complex = compileAssetArchive({ diff --git a/tests/unit/wp5-01-asset-compiler.test.ts b/tests/unit/wp5-01-asset-compiler.test.ts index 9bd5303..a20fef3 100644 --- a/tests/unit/wp5-01-asset-compiler.test.ts +++ b/tests/unit/wp5-01-asset-compiler.test.ts @@ -190,6 +190,27 @@ describe("TDD-WP5-MAN-001 readonly asset compiler", () => { expect(repeated.report.derived_files).toEqual({ created: 0, reused: 4, total: 4 }); }); + it("safely relocates legacy absolute font package paths after an archive move", () => { + const fixture = createFixture(); + const catalogPath = join(fixture.sourceRoot, "fonts", "reports", "font_panel_catalog.csv"); + const metadata = JSON.parse(readFileSync(join(fixture.sourceRoot, "fonts", "resources", "font_packages", "FONT001_Test", "metadata.json"), "utf8")) as { local_sha256: string }; + csv(catalogPath, [{ + candidate_id: "FONT001", + display_name: "Test Font", + font_family: "Dada Test", + local_sha256: metadata.local_sha256, + panel_order: "1", + resource_dir: "C:/Users/legacy/Desktop/sticker_text/fonts/resources/font_packages/FONT001_Test", + resource_status: "verified_extracted", + }]); + + expect(() => compileAssetArchive({ + manifestPath: fixture.manifestPath, + outputDirectory: fixture.outputRoot, + releaseVersion: "fixture-v1", + })).not.toThrow(); + }); + it("rejects evidence collections, traversal and output inside a source root", () => { const fixture = createFixture(); const manifest = JSON.parse(readFileSync(fixture.manifestPath, "utf8")) as { collections: unknown[] };