From f7bf1b026a54c8c6dd4d990c8707ddf6a06e58fb Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 20:54:41 -0700 Subject: [PATCH] feat(editor): add --validate-project-open-only release gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI-friendly check that exits 1 if any proprietary Blizzard asset (.m2/.skin/.wmo/.blp/.dbc) remains in . Designed as the final gate before shipping a wholly open-format project release — once it returns exit 0 the project ships only WOM/WOB/PNG/JSON content with no Blizzard binary payloads at rest. Reports per-extension counts and lists the offending files (capped at 50 to avoid flooding the terminal on a wholly unmigrated project). Verified: pass case on open-only test project (exit 0); fail case after dropping fake .blp + .m2 (exit 1, both files listed under their extensions). Brings command count to 173. --- tools/editor/main.cpp | 60 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index ac0eeef0..66b4d13a 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -674,6 +674,8 @@ static void printUsage(const char* argv0) { std::printf(" Tree view of every zone in a project with quick counts (no --json)\n"); std::printf(" --info-project-bytes [--json]\n"); std::printf(" Per-zone byte rollup with proprietary-vs-open category split (size audit)\n"); + std::printf(" --validate-project-open-only \n"); + std::printf(" Exit 1 if any proprietary Blizzard assets (.m2/.wmo/.blp/.dbc) remain — release gate\n"); std::printf(" --info-zone-bytes [--json]\n"); std::printf(" Per-file size breakdown grouped by category, sorted largest-first\n"); std::printf(" --info-zone-extents [--json]\n"); @@ -861,6 +863,7 @@ int main(int argc, char* argv[]) { "--unpack-wcp", "--pack-wcp", "--validate", "--validate-wom", "--validate-wob", "--validate-woc", "--validate-whm", "--validate-all", "--validate-project", + "--validate-project-open-only", "--bench-validate-project", "--bench-bake-project", "--validate-glb", "--info-glb", "--info-glb-tree", "--info-glb-bytes", "--validate-jsondbc", "--check-glb-bounds", "--validate-stl", @@ -7252,6 +7255,63 @@ int main(int argc, char* argv[]) { } std::printf("\n %d zone(s) failed validation\n", projectFailedZones); return 1; + } else if (std::strcmp(argv[i], "--validate-project-open-only") == 0 && i + 1 < argc) { + // Release gate. Walks every file in and exits + // 1 if any proprietary Blizzard asset is present (.m2, .skin, + // .wmo, .blp, .dbc). Designed for CI to enforce a + // "no-proprietary-assets" release condition once a project + // has fully migrated to the open WOM/WOB/PNG/JSON formats. + std::string projectDir = argv[++i]; + namespace fs = std::filesystem; + if (!fs::exists(projectDir) || !fs::is_directory(projectDir)) { + std::fprintf(stderr, + "validate-project-open-only: %s is not a directory\n", + projectDir.c_str()); + return 1; + } + // Standard set of proprietary extensions. Mirrors the + // "(proprietary)" categories used by --info-project-bytes. + static const std::set propExt = { + ".m2", ".skin", ".wmo", ".blp", ".dbc", + }; + std::map byExt; + std::vector hits; + std::error_code ec; + for (const auto& e : fs::recursive_directory_iterator(projectDir, ec)) { + if (!e.is_regular_file()) continue; + std::string ext = e.path().extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (!propExt.count(ext)) continue; + byExt[ext]++; + std::string rel = fs::relative(e.path(), projectDir, ec).string(); + if (ec) rel = e.path().string(); + hits.push_back(rel); + } + std::sort(hits.begin(), hits.end()); + std::printf("validate-project-open-only: %s\n", projectDir.c_str()); + if (hits.empty()) { + std::printf(" PASSED — no proprietary Blizzard assets present\n"); + return 0; + } + std::printf(" FAILED — %zu proprietary file(s) remain\n", hits.size()); + std::printf("\n Per-extension:\n"); + for (const auto& [ext, count] : byExt) { + std::printf(" %-6s : %d\n", ext.c_str(), count); + } + std::printf("\n Files (sorted):\n"); + // Cap the file list at 50 entries so a wholly unmigrated + // project doesn't fill the user's terminal. + size_t shown = 0; + for (const auto& h : hits) { + if (shown >= 50) { + std::printf(" ... and %zu more\n", hits.size() - shown); + break; + } + std::printf(" - %s\n", h.c_str()); + shown++; + } + return 1; } else if (std::strcmp(argv[i], "--bench-validate-project") == 0 && i + 1 < argc) { // Time --validate-project per zone. Reports avg/min/max // latency so users can spot zones that are unusually slow