Files
EpicNext-Cms/docs/superpowers/plans/2026-08-02-production-furni-assets.md
T

5.1 KiB

Production Furni Assets Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make every furni import write the icon, Nitro bundle, and FurnitureData entry into the directories served by the production client under /var/www/Gamedata.

Architecture: Extend the central furni asset resolver with a separate Gamedata layout while preserving CMS-local and Nitro-Files targets. Both import paths consume the same resolved targets; FurnitureData resolves the same Gamedata root independently so metadata and binary assets stay aligned.

Tech Stack: TypeScript 7, Node.js filesystem APIs, Vitest 4, Next.js 16.

Global Constraints

  • Preserve all existing nitro_files_root, furni_*_dir, and furni_data_mirror_path behavior.
  • Auto-detect /var/www/Gamedata only when the directory exists; allow gamedata_root to override it.
  • Do not copy source SWFs into the Gamedata tree.
  • Report live mirror failures in the import warnings.
  • Use test-first development and run the production build before completion.

Task 1: Resolve the production Gamedata layout

Files:

  • Create: src/lib/services/furni-asset-dirs.test.ts
  • Modify: src/lib/services/furni-asset-dirs.ts
  • Modify: src/lib/services/furni-data.ts
  • Modify: src/app/admin/settings/cms-settings-config.ts

Interfaces:

  • Produces: getGamedataRoot(): Promise<string>.

  • Produces: Gamedata mirror entries from getFurniAssetWriteTargets() with <root>/icons and <root>/bundled/furniture.

  • Consumes: siteSettings.get("gamedata_root", "") and existsSync(DEFAULT_GAMEDATA_ROOT).

  • Step 1: Write failing resolver tests

Mock siteSettings.get and existsSync, then assert that a configured Gamedata root produces:

expect(targets.mirrorDirs).toContainEqual({
  swfDir: targets.swfDir,
  iconDir: path.join(gamedataRoot, "icons"),
  nitroDir: path.join(gamedataRoot, "bundled/furniture"),
});

Also assert that an absent unconfigured root adds no Gamedata mirror and that a duplicate primary destination is de-duplicated.

  • Step 2: Run the resolver test and verify RED

Run: pnpm exec vitest run --coverage=false src/lib/services/furni-asset-dirs.test.ts

Expected: FAIL because gamedata_root is not read and the Gamedata mirror is absent.

  • Step 3: Implement the Gamedata resolver

Add DEFAULT_GAMEDATA_ROOT, getGamedataRoot(), and a pure Gamedata mapping that uses the primary swfDir while mapping icons and Nitro bundles to the live locations. Merge this candidate with the existing Nitro-Files candidate and remove identical directory triples.

  • Step 4: Extend FurnitureData and the settings form

Make getFurnitureDataWritePaths() include <gamedata_root>/config/FurnitureData.json, after any explicit furni_data_mirror_path. Add a documented gamedata_root text setting with /var/www/Gamedata as the placeholder.

  • Step 5: Run resolver tests and verify GREEN

Run: pnpm exec vitest run --coverage=false src/lib/services/furni-asset-dirs.test.ts

Expected: PASS.

  • Step 6: Commit Task 1
fix: map furni imports to production gamedata

Task 2: Mirror manual Nitro uploads

Files:

  • Create: src/lib/services/upload-import.test.ts
  • Modify: src/lib/services/upload-import.ts

Interfaces:

  • Consumes: getFurniAssetWriteTargets(): Promise<FurniAssetWriteTargets>.

  • Produces: manual upload copies at every unique mirrorDirs[].iconDir and mirrorDirs[].nitroDir.

  • Step 1: Write a failing manual-upload test

Mock the database and metadata dependencies, provide temporary primary and Gamedata directories, call uploadSingleFurni, and assert:

await expect(fs.readFile(path.join(liveNitroDir, "chair.nitro"))).resolves.toEqual(nitroBuffer);
await expect(fs.readFile(path.join(liveIconDir, "chair_icon.png"))).resolves.toEqual(iconBuffer);
  • Step 2: Run the upload test and verify RED

Run: pnpm exec vitest run --coverage=false src/lib/services/upload-import.test.ts

Expected: FAIL because manual uploads currently write only to the primary CMS directories.

  • Step 3: Implement manual mirroring

Resolve all write targets, create their directories through the existing ensureDirectories(), and copy the successfully written primary Nitro and optional icon files to each unique mirror. Catch each copy error separately and append a warning containing the destination path.

  • Step 4: Run the upload test and verify GREEN

Run: pnpm exec vitest run --coverage=false src/lib/services/upload-import.test.ts

Expected: PASS.

  • Step 5: Run focused and full verification

Run:

pnpm exec vitest run --coverage=false src/lib/services/furni-asset-dirs.test.ts src/lib/services/upload-import.test.ts src/lib/services/furni-import.test.ts
pnpm typecheck
pnpm test
pnpm build

Expected: every command exits with status 0.

  • Step 6: Commit Task 2
fix: mirror manual furni uploads to live assets