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, andfurni_data_mirror_pathbehavior. - Auto-detect
/var/www/Gamedataonly when the directory exists; allowgamedata_rootto 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>/iconsand<root>/bundled/furniture. -
Consumes:
siteSettings.get("gamedata_root", "")andexistsSync(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[].iconDirandmirrorDirs[].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