@@ -1250,13 +1250,90 @@ namespace vix::commands
12501250 }
12511251 }
12521252
1253+ struct PublicAssetsInfo
1254+ {
1255+ fs::path publicDir{};
1256+ bool exists{false };
1257+ bool isDirectory{false };
1258+ bool indexHtmlExists{false };
1259+ bool appCssExists{false };
1260+ bool appJsExists{false };
1261+ bool statusHtmlExists{false };
1262+ bool statusCssExists{false };
1263+ bool statusJsExists{false };
1264+ std::vector<std::string> missingFiles{};
1265+ };
1266+
1267+ PublicAssetsInfo detect_public_assets ()
1268+ {
1269+ PublicAssetsInfo info;
1270+ info.publicDir = fs::current_path () / " public" ;
1271+
1272+ std::error_code ec;
1273+
1274+ info.exists = fs::exists (info.publicDir , ec);
1275+ info.isDirectory = info.exists && fs::is_directory (info.publicDir , ec);
1276+
1277+ if (!info.isDirectory )
1278+ {
1279+ info.missingFiles .push_back (" public/" );
1280+ return info;
1281+ }
1282+
1283+ const fs::path indexHtml = info.publicDir / " index.html" ;
1284+ const fs::path appCss = info.publicDir / " app.css" ;
1285+ const fs::path appJs = info.publicDir / " app.js" ;
1286+ const fs::path statusHtml = info.publicDir / " status.html" ;
1287+ const fs::path statusCss = info.publicDir / " status.css" ;
1288+ const fs::path statusJs = info.publicDir / " status.js" ;
1289+
1290+ info.indexHtmlExists =
1291+ fs::exists (indexHtml, ec) && fs::is_regular_file (indexHtml, ec);
1292+
1293+ info.appCssExists =
1294+ fs::exists (appCss, ec) && fs::is_regular_file (appCss, ec);
1295+
1296+ info.appJsExists =
1297+ fs::exists (appJs, ec) && fs::is_regular_file (appJs, ec);
1298+
1299+ info.statusHtmlExists =
1300+ fs::exists (statusHtml, ec) && fs::is_regular_file (statusHtml, ec);
1301+
1302+ info.statusCssExists =
1303+ fs::exists (statusCss, ec) && fs::is_regular_file (statusCss, ec);
1304+
1305+ info.statusJsExists =
1306+ fs::exists (statusJs, ec) && fs::is_regular_file (statusJs, ec);
1307+
1308+ if (!info.indexHtmlExists )
1309+ info.missingFiles .push_back (" public/index.html" );
1310+
1311+ if (!info.appCssExists )
1312+ info.missingFiles .push_back (" public/app.css" );
1313+
1314+ if (!info.appJsExists )
1315+ info.missingFiles .push_back (" public/app.js" );
1316+
1317+ if (!info.statusHtmlExists )
1318+ info.missingFiles .push_back (" public/status.html" );
1319+
1320+ if (!info.statusCssExists )
1321+ info.missingFiles .push_back (" public/status.css" );
1322+
1323+ if (!info.statusJsExists )
1324+ info.missingFiles .push_back (" public/status.js" );
1325+
1326+ return info;
1327+ }
1328+
12531329 int run_production_doctor (bool jsonOut)
12541330 {
12551331 vix::cli::util::section (std::cout, " Production Doctor" );
12561332
12571333 const auto projectName = read_project_name ().value_or (" unknown" );
12581334 const auto buildDir = detect_build_dir ();
12591335 const auto binary = detect_binary_path (projectName);
1336+ const auto publicAssets = detect_public_assets ();
12601337
12611338 const auto service = detect_systemd_service (
12621339 projectName,
@@ -1408,7 +1485,61 @@ namespace vix::commands
14081485 vix::cli::util::kv (std::cout, " TLS" , tls ? " enabled" : " unknown" );
14091486 vix::cli::util::kv (std::cout, " Local health" , localHealth ? " ok" : " unknown" );
14101487 vix::cli::util::kv (std::cout, " Public health" , publicHealth ? " ok" : " unknown" );
1411- vix::cli::util::section (std::cout, " Production Readiness" );
1488+ vix::cli::util::section (std::cout, " Public Assets" );
1489+
1490+ vix::cli::util::kv (
1491+ std::cout,
1492+ " Public assets" ,
1493+ publicAssets.isDirectory ? " detected" : " missing" );
1494+
1495+ vix::cli::util::kv (
1496+ std::cout,
1497+ " Public directory" ,
1498+ publicAssets.publicDir .string ());
1499+
1500+ vix::cli::util::kv (
1501+ std::cout,
1502+ " index.html" ,
1503+ publicAssets.indexHtmlExists ? " ok" : " missing" );
1504+
1505+ vix::cli::util::kv (
1506+ std::cout,
1507+ " app.css" ,
1508+ publicAssets.appCssExists ? " ok" : " missing" );
1509+
1510+ vix::cli::util::kv (
1511+ std::cout,
1512+ " app.js" ,
1513+ publicAssets.appJsExists ? " ok" : " missing" );
1514+
1515+ vix::cli::util::kv (
1516+ std::cout,
1517+ " status.html" ,
1518+ publicAssets.statusHtmlExists ? " ok" : " missing" );
1519+
1520+ vix::cli::util::kv (
1521+ std::cout,
1522+ " status.css" ,
1523+ publicAssets.statusCssExists ? " ok" : " missing" );
1524+
1525+ vix::cli::util::kv (
1526+ std::cout,
1527+ " status.js" ,
1528+ publicAssets.statusJsExists ? " ok" : " missing" );
1529+
1530+ if (!publicAssets.missingFiles .empty ())
1531+ {
1532+ for (const auto &file : publicAssets.missingFiles )
1533+ {
1534+ vix::cli::util::warn_line (
1535+ std::cerr,
1536+ " Missing public asset: " + file);
1537+ }
1538+
1539+ vix::cli::util::warn_line (
1540+ std::cerr,
1541+ " Fix: create the missing files in public/ or remove static UI expectations from this app." );
1542+ }
14121543
14131544 vix::cli::util::kv (
14141545 std::cout,
@@ -1483,6 +1614,15 @@ namespace vix::commands
14831614 out[" environment" ] = environment.value_or (" " );
14841615 out[" websocket_port" ] = websocketPort.value_or (" " );
14851616 out[" proxy_target" ] = proxyTarget.value_or (" " );
1617+ out[" public_assets_detected" ] = publicAssets.isDirectory ;
1618+ out[" public_directory" ] = publicAssets.publicDir .string ();
1619+ out[" public_index_html" ] = publicAssets.indexHtmlExists ;
1620+ out[" public_app_css" ] = publicAssets.appCssExists ;
1621+ out[" public_app_js" ] = publicAssets.appJsExists ;
1622+ out[" public_status_html" ] = publicAssets.statusHtmlExists ;
1623+ out[" public_status_css" ] = publicAssets.statusCssExists ;
1624+ out[" public_status_js" ] = publicAssets.statusJsExists ;
1625+ out[" public_missing_files" ] = publicAssets.missingFiles ;
14861626
14871627 for (const auto &item : readiness)
14881628 {
0 commit comments