diff --git a/.gitignore b/.gitignore index f15703f..033dae1 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,8 @@ /node_modules/ /frontend_dist/ *.tsbuildinfo +*.zip .idea/ /third_party/ +/verification/ +/frontend/test-results/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b622ad..3af3ed7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,23 @@ if(ADMINIVE_ENABLE_REAL_DROGON_TESTS) find_package(Drogon CONFIG REQUIRED) endif() include("${CMAKE_CURRENT_LIST_DIR}/cmake/AdminiveVerification.cmake") -add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/third_party/Structive") +set(_adminive_embedded_structive_dir "${CMAKE_CURRENT_LIST_DIR}/third_party/Structive") +set(_adminive_sibling_structive_dir "${CMAKE_CURRENT_LIST_DIR}/../Structive") +if(EXISTS "${_adminive_embedded_structive_dir}/CMakeLists.txt") + set(_adminive_default_structive_dir "${_adminive_embedded_structive_dir}") +elseif(EXISTS "${_adminive_sibling_structive_dir}/CMakeLists.txt") + set(_adminive_default_structive_dir "${_adminive_sibling_structive_dir}") +else() + set(_adminive_default_structive_dir "${_adminive_embedded_structive_dir}") +endif() +set(ADMINIVE_STRUCTIVE_SOURCE_DIR "${_adminive_default_structive_dir}" CACHE PATH "Structive source tree used by Adminive") +get_filename_component(ADMINIVE_STRUCTIVE_SOURCE_DIR "${ADMINIVE_STRUCTIVE_SOURCE_DIR}" ABSOLUTE) +if(NOT EXISTS "${ADMINIVE_STRUCTIVE_SOURCE_DIR}/CMakeLists.txt") + message(FATAL_ERROR "Structive source tree not found at ${ADMINIVE_STRUCTIVE_SOURCE_DIR}. Set ADMINIVE_STRUCTIVE_SOURCE_DIR explicitly.") +endif() +if(NOT TARGET structive::property_core) + add_subdirectory("${ADMINIVE_STRUCTIVE_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/third_party/Structive") +endif() add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/backend") set(adminive_package_includes "/.agents/" @@ -28,6 +44,8 @@ set(adminive_package_includes set(adminive_package_excludes "/.git/" "/third_party/Structive/.git/" + "/third_party/Structive/cmake-build-*/" + "/third_party/Structive/verification/" "/.idea/" "/build/" "/cmake-build-*/" @@ -43,7 +61,35 @@ set(adminive_package_excludes "*.zip" ) include("${CMAKE_CURRENT_LIST_DIR}/cmake/PackageZip.cmake") -add_project_zip_target(package_zip "${CMAKE_CURRENT_LIST_DIR}/Adminive.zip" "${CMAKE_CURRENT_LIST_DIR}" adminive_package_includes adminive_package_excludes) +if("${ADMINIVE_STRUCTIVE_SOURCE_DIR}" STREQUAL "${_adminive_embedded_structive_dir}") + add_project_zip_target(package_zip "${CMAKE_CURRENT_LIST_DIR}/Adminive.zip" "${CMAKE_CURRENT_LIST_DIR}" adminive_package_includes adminive_package_excludes) +else() + set(adminive_structive_package_includes + "/.agents/" + "/.gitignore" + "/AGENTS.md" + "/cmake/" + "/core/" + "/docs/" + "/extensions/" + "/install_consumer/" + "/tests/" + "/CMakeLists.txt" + "/README.md" + "/README.zh-CN.md" + "/RELEASE.md" + "/THIRD_PARTY_NOTICES.md" + ) + set(adminive_structive_package_excludes + "/.git/" + "/.idea/" + "/build/" + "/cmake-build-*/" + "/verification/" + "*.zip" + ) + add_project_zip_target(package_zip "${CMAKE_CURRENT_LIST_DIR}/Adminive.zip" "${CMAKE_CURRENT_LIST_DIR}" adminive_package_includes adminive_package_excludes "${ADMINIVE_STRUCTIVE_SOURCE_DIR}" "third_party/Structive" adminive_structive_package_includes adminive_structive_package_excludes) +endif() if(BUILD_TESTING) add_test(NAME Adminive_Install_Consumer_Test COMMAND "${CMAKE_COMMAND}" "-DADMINIVE_BINARY_DIR=${CMAKE_BINARY_DIR}" "-DADMINIVE_CONFIG=$" -P "${CMAKE_CURRENT_LIST_DIR}/cmake/RunInstallConsumer.cmake") set_tests_properties(Adminive_Install_Consumer_Test PROPERTIES LABELS "install;package") diff --git a/README.md b/README.md index 1f98575..7bdb2cc 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,8 @@ target_link_libraries(drogon_target PRIVATE Adminive::Drogon) using Json = nlohmann::json; ``` +源码树内置 nlohmann/json、magic_enum 和 cpp-httplib;Boost.PFR 继续使用外部头文件。测试环境没有可发现的 `Boost::headers` CMake target 时,通过 `-DADMINIVE_BOOST_PFR_INCLUDE_DIR=` 指向包含 `boost/pfr.hpp` 的目录。缺少该依赖时配置阶段会明确失败,避免适配器测试被静默跳过。 + ### JSON 适配 外部 JSON 类型通过 `Json_Adapter` 接入。适配器负责对象、数组、标量、字段访问、解析和输出。所有转换入口显式携带 JSON 类型: @@ -366,7 +368,7 @@ adminive::Managed_Value config; adminive::Managed_Value> config; ``` -描述协议 `adminive.resource` 当前版本为 4。字段输出 `writable` 与最终 `synchronized` 信息,不再包含旧的 `lock_mode`。`writable` 表示 Adminive managed model 的 intrinsic mutability;`synchronized` 只表示该 writable 字段是否贡献 Structive 同步域,与前端是否 editable 是两个独立维度。 +描述协议 `adminive.resource` 当前版本为 5。字段输出 `writable` 与最终 `synchronized` 信息,不再包含旧的 `lock_mode`。`writable` 表示 Adminive managed model 的 intrinsic mutability;`synchronized` 只表示该 writable 字段是否贡献 Structive 同步域,与前端是否 editable 是两个独立维度。 数据库事务不能替代运行时同步。数据库负责持久化原子性;Structive synchronization 负责当前进程中的 mutable consistency;`Resource_Transaction` 负责 Adminive 更新过程中外部副作用的 prepare/commit/rollback。三者职责保持独立。 @@ -812,10 +814,15 @@ Adminive/ ```powershell npm --prefix .\frontend ci npm --prefix .\frontend run build +npm --prefix .\frontend audit --omit=dev ``` +发布前必须审查生产依赖报告:优先消除直接依赖和 critical 项;上游暂无修复的传递项要记录来源与暴露面,不能用 `npm audit fix --force` 做未经验证的跨主版本升级。`package.json` 的 override 只用于不进入浏览器运行时、且已通过正常 `npm ci`、production build 和 E2E 验证的传递依赖修复。 + ## 后端 +源码构建会优先使用 `third_party/Structive` 内嵌目录;如果两个库在同一父目录下独立维护,则自动使用同级 `../Structive`。其他布局通过 `-DADMINIVE_STRUCTIVE_SOURCE_DIR=` 显式指定,不需要复制第二份 Structive 源码。 + ```powershell cmake -S . -B build -G Ninja cmake --build build @@ -825,6 +832,8 @@ ctest --test-dir build --output-on-failure 测试使用 `backend/test_support/adminive_test.hpp` 的 `ADMINIVE_CHECK`,不会像标准 `assert()` 一样在 Release `NDEBUG` 下被移除。旧的 `Synchronized_Value`/`Resource_Lock_Scope` 测试实现已经删除;仍有意义的嵌套同步、无锁字段、读写能力和多线程序列化用例由当前 `Managed_Value` 测试覆盖,不恢复旧 API。 +核心测试各自保护一层边界:`Core_Adapter` 使用非 nlohmann 的最小 JSON 实现验证 Core 真正与第三方解耦;`Managed` 验证 Structive topology、回调生命周期和并发;`View_Schema` 验证后端字段组合、固定列/查询授权、Manifest 与 AMIS 翻译;`Safety`/`Advanced_Adapter` 验证输入、敏感字段、Object/Value/Control/Polymorphic/Reflection Adapter 和事务恢复;Httplib/Drogon 测 transport 一致性;Gallery 只测示例注册表与真实端点,不重复 Core 算法。新增用例应放进拥有该契约的文件,不再扩张单一大杂烩测试。 + CTest 使用 label 区分 `unit`、`protocol`、`http`、`install`、`package`、`header`、`concurrency`、`managed` 等测试。全部 Adminive Core 公共头和 5 个 Service Adapter 公共头都注册独立 include 编译测试,避免 umbrella header 掩盖缺失依赖。`Adminive_Install_Consumer_Test` 会先安装到构建目录内的固定测试 prefix,再从独立 `install_consumer` 工程只通过 `find_package(Adminive)` 同时构建 `Adminive::Core` 与 `Adminive::Default`/Httplib consumer。安装包不再把仓库内置的 nlohmann/json、magic_enum、cpp-httplib 复制到公共 include 命名空间;测试会用独立依赖 package fixture 验证导出目标确实通过 `find_dependency` 解析外部依赖,并检查安装 prefix 中不存在这些 bundled header。`Adminive_Package_Zip_Test` 会实际生成源码 ZIP,验证根目录精确文件匹配、release/license metadata、无残留嵌套工程且已删除的旧同步实现不会重新进入发布包。 Clang/GNU sanitizer: @@ -885,6 +894,8 @@ http://127.0.0.1:9999 cmake --build build --target package_zip ``` +当 `ADMINIVE_STRUCTIVE_SOURCE_DIR` 指向同级外部源码树时,打包目标只把 Structive 发布清单中的源码、文档、测试和 Codex 约束暂存到归档内的 `third_party/Structive`;不会复制 `.git`、验证构建目录或在工作树中制造第二份依赖。内嵌布局继续直接按同一归档路径打包。 + 压缩函数依次接收目标名、输出文件、根目录、包含列表变量名和排除列表变量名。只扫描包含列表指定的路径,再按相对于根目录的规则排除: ```cmake diff --git a/RELEASE.md b/RELEASE.md index 02c49ff..cf4b2c2 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -7,6 +7,8 @@ Adminive intentionally uses two dependency modes: - Source-tree build/test mode: nlohmann/json 3.12.0, magic_enum 0.9.8, and cpp-httplib 0.48.0 use pinned bundled headers under `backend/service/third_party/include`. - Installed-package mode: adapter components use external CMake packages. The bundled headers are not installed into the consumer's generic include namespace. +Structive may be checked out under `third_party/Structive` for a standalone Adminive source tree or maintained as a sibling checkout selected by `ADMINIVE_STRUCTIVE_SOURCE_DIR`. The source archive always stages the selected Structive release files under `third_party/Structive`, without copying repository history or verification output into either working tree. + Installed component dependencies are: | Adminive component | External dependency | @@ -43,5 +45,6 @@ python scripts/verify.py --real-drogon 4. Run sanitizer gates supported by the compiler. 5. Run `python scripts/verify.py --real-drogon` on a release environment with real Drogon packages. 6. If frontend code changed, run frontend unit, production build, Chromium E2E, and Edge E2E on Windows. -7. Verify the installed prefix contains Adminive headers and release/license metadata but not bundled nlohmann/json, magic_enum, or cpp-httplib headers. -8. Generate the source archive without build output and with China Standard Time timestamps. +7. Run `npm --prefix frontend audit --omit=dev`; eliminate direct and critical findings where a compatible fix exists, and document unfixed upstream exposure instead of using an unverified forced major upgrade. +8. Verify the installed prefix contains Adminive headers and release/license metadata but not bundled nlohmann/json, magic_enum, or cpp-httplib headers. +9. Generate the source archive without build output and with China Standard Time timestamps. diff --git a/backend/library/CMakeLists.txt b/backend/library/CMakeLists.txt index 036dd22..ab9d656 100644 --- a/backend/library/CMakeLists.txt +++ b/backend/library/CMakeLists.txt @@ -29,4 +29,4 @@ if (BUILD_TESTING) endif () install(TARGETS Adminive EXPORT AdminiveCoreTargets) install(DIRECTORY "${adminive_library_include_dir}/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") -install(DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/../../third_party/Structive/core/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +install(DIRECTORY "${ADMINIVE_STRUCTIVE_SOURCE_DIR}/core/include/" DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") diff --git a/backend/library/include/adminive/json.hpp b/backend/library/include/adminive/json.hpp index cc6c498..4b17848 100644 --- a/backend/library/include/adminive/json.hpp +++ b/backend/library/include/adminive/json.hpp @@ -10,10 +10,14 @@ #include #include #include +#include #include #include #include namespace adminive { +inline constexpr std::string_view resource_protocol = "adminive.resource"; +inline constexpr int resource_protocol_version = 5; + struct Update_Result { bool success{}; std::string message; @@ -437,8 +441,8 @@ Json model_descriptor_json(const Model* defaults = nullptr) { }(), ...); }, descriptor.fields()); Json result = json_object(); - json_set(result, "protocol", "adminive.resource"); - json_set(result, "protocol_version", 5); + json_set(result, "protocol", resource_protocol); + json_set(result, "protocol_version", resource_protocol_version); json_set(result, "name", descriptor.name()); json_set(result, "label", descriptor.label()); json_set(result, "value_type", "object"); diff --git a/backend/library/include/adminive/status.hpp b/backend/library/include/adminive/status.hpp index a5843e2..1979bf7 100644 --- a/backend/library/include/adminive/status.hpp +++ b/backend/library/include/adminive/status.hpp @@ -1,9 +1,13 @@ #pragma once #include "adminive/json.hpp" #include +#include #include #include namespace adminive { +inline constexpr std::string_view status_protocol = "adminive.status"; +inline constexpr int status_protocol_version = 1; + template struct Status_Value { using value_type = T; @@ -62,8 +66,8 @@ Json to_status_descriptor_json() { }(), ...); }, descriptor.fields()); Json result = json_object(); - json_set(result, "protocol", "adminive.status"); - json_set(result, "protocol_version", 1); + json_set(result, "protocol", status_protocol); + json_set(result, "protocol_version", status_protocol_version); json_set(result, "name", descriptor.name()); json_set(result, "label", descriptor.label()); json_set(result, "value_type", "status_object"); diff --git a/backend/library/include/adminive/view_json.hpp b/backend/library/include/adminive/view_json.hpp index a555aec..150e6ab 100644 --- a/backend/library/include/adminive/view_json.hpp +++ b/backend/library/include/adminive/view_json.hpp @@ -3,7 +3,13 @@ #include "adminive/json.hpp" #include "adminive/view.hpp" #include +#include namespace adminive { +inline constexpr std::string_view view_protocol = "adminive.view"; +inline constexpr int view_protocol_version = 1; +inline constexpr std::string_view composition_manifest_protocol = "adminive.composition-manifest"; +inline constexpr int composition_manifest_protocol_version = 1; + inline std::string_view composition_node_kind_name(Composition_Node_Kind value) noexcept { switch(value) { case Composition_Node_Kind::slot: @@ -72,8 +78,8 @@ template Json to_view_json(const Composition_View& view) { validate_composition_view(view); Json result = json_object(); - json_set(result, "protocol", "adminive.view"); - json_set(result, "protocol_version", 1); + json_set(result, "protocol", view_protocol); + json_set(result, "protocol_version", view_protocol_version); json_set(result, "kind", "composition"); json_set(result, "name", view.name); if(!view.title.empty()) { @@ -109,8 +115,8 @@ Json to_composition_manifest_json(const Composition_View& view, const std::vecto json_set(slots, contract.name, std::move(item)); } Json result = json_object(); - json_set(result, "protocol", "adminive.composition-manifest"); - json_set(result, "protocol_version", 1); + json_set(result, "protocol", composition_manifest_protocol); + json_set(result, "protocol_version", composition_manifest_protocol_version); json_set(result, "view", to_view_json(view)); json_set(result, "slots", std::move(slots)); return result; @@ -202,8 +208,8 @@ template Json to_view_json(const Form_View& view) { validate_form_view(view); Json result = json_object(); - json_set(result, "protocol", "adminive.view"); - json_set(result, "protocol_version", 1); + json_set(result, "protocol", view_protocol); + json_set(result, "protocol_version", view_protocol_version); json_set(result, "kind", "form"); json_set(result, "name", view.name); json_set(result, "title", view.title); @@ -261,8 +267,8 @@ template Json to_view_json(const Table_View& view) { validate_table_view(view); Json result = json_object(); - json_set(result, "protocol", "adminive.view"); - json_set(result, "protocol_version", 1); + json_set(result, "protocol", view_protocol); + json_set(result, "protocol_version", view_protocol_version); json_set(result, "kind", "table"); append_table_view_json(result, view); return result; @@ -271,8 +277,8 @@ template Json to_view_json(const Collection_View& view) { validate_collection_view(view); Json result = json_object(); - json_set(result, "protocol", "adminive.view"); - json_set(result, "protocol_version", 1); + json_set(result, "protocol", view_protocol); + json_set(result, "protocol_version", view_protocol_version); json_set(result, "kind", "collection"); json_set(result, "mode", std::string(collection_mode_name(view.mode))); append_table_view_json(result, view.table); diff --git a/backend/library/tests/core_adapter_test.cpp b/backend/library/tests/core_adapter_test.cpp index 292e1ad..afc3fa1 100644 --- a/backend/library/tests/core_adapter_test.cpp +++ b/backend/library/tests/core_adapter_test.cpp @@ -274,6 +274,8 @@ int main() { using Json = adapter_test::Mini_Json; using Config = adapter_test::Config; const Json descriptor = adminive::to_descriptor_json_with_defaults(); + ADMINIVE_CHECK((adminive::json_get(adminive::Json_Adapter::at(descriptor, "protocol")) == adminive::resource_protocol)); + ADMINIVE_CHECK((adminive::json_get(adminive::Json_Adapter::at(descriptor, "protocol_version")) == adminive::resource_protocol_version)); const auto& fields = adminive::Json_Adapter::at(descriptor, "fields"); ADMINIVE_CHECK((adminive::json_get(adminive::Json_Adapter::at(adminive::Json_Adapter::at(fields, 0), "name")) == "worker_count")); ADMINIVE_CHECK((adminive::json_get(adminive::Json_Adapter::at(adminive::Json_Adapter::at(fields, 0), "default")) == 4)); diff --git a/backend/service/CMakeLists.txt b/backend/service/CMakeLists.txt index e7be6da..4f17203 100644 --- a/backend/service/CMakeLists.txt +++ b/backend/service/CMakeLists.txt @@ -21,6 +21,17 @@ add_library(Adminive_BoostPfr INTERFACE) set_target_properties(Adminive_BoostPfr PROPERTIES EXPORT_NAME BoostPfr) add_library(Adminive::BoostPfr ALIAS Adminive_BoostPfr) target_link_libraries(Adminive_BoostPfr INTERFACE Adminive::Service "$") +find_package(Boost CONFIG QUIET) +if(TARGET Boost::headers) + target_link_libraries(Adminive_BoostPfr INTERFACE "$") +else() + set(ADMINIVE_BOOST_PFR_INCLUDE_DIR "" CACHE PATH "Directory containing boost/pfr.hpp for source-tree tests") + if(EXISTS "${ADMINIVE_BOOST_PFR_INCLUDE_DIR}/boost/pfr.hpp") + target_include_directories(Adminive_BoostPfr INTERFACE "$") + elseif(BUILD_TESTING) + message(FATAL_ERROR "Boost.PFR is required by the adapter contract tests. Install Boost.PFR or set ADMINIVE_BOOST_PFR_INCLUDE_DIR to the directory containing boost/pfr.hpp.") + endif() +endif() add_library(Adminive_Httplib INTERFACE) set_target_properties(Adminive_Httplib PROPERTIES EXPORT_NAME Httplib) add_library(Adminive::Httplib ALIAS Adminive_Httplib) diff --git a/backend/service/tests/advanced_adapter_test.cpp b/backend/service/tests/advanced_adapter_test.cpp index eff6751..fdbe7d8 100644 --- a/backend/service/tests/advanced_adapter_test.cpp +++ b/backend/service/tests/advanced_adapter_test.cpp @@ -331,19 +331,7 @@ int main() { const auto frontend_secret = adminive::to_frontend_json(secret); ADMINIVE_CHECK(!frontend_secret.contains("secret")); ADMINIVE_CHECK(frontend_secret.at("visible") == "public"); - bool duplicate_rejected{}; - try { - static_cast(adminive::to_descriptor_json()); - } catch(const std::invalid_argument&) { - duplicate_rejected = true; - } - ADMINIVE_CHECK(duplicate_rejected); - bool invalid_sort_rejected{}; - try { - adminive::validate_table_view(adminive::describe_table_view()); - } catch(const std::invalid_argument&) { - invalid_sort_rejected = true; - } - ADMINIVE_CHECK(invalid_sort_rejected); + ADMINIVE_CHECK_THROWS_AS((adminive::to_descriptor_json()), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS((adminive::validate_table_view(adminive::describe_table_view())), std::invalid_argument); return 0; } diff --git a/backend/service/tests/managed_test.cpp b/backend/service/tests/managed_test.cpp index 061b334..e03a1bb 100644 --- a/backend/service/tests/managed_test.cpp +++ b/backend/service/tests/managed_test.cpp @@ -2,6 +2,8 @@ #include "adminive/http.hpp" #include #include "adminive_test.hpp" +#include +#include #include #include #include @@ -50,6 +52,22 @@ concept Reference_Escaping_Read = requires(Field field) { return value; }); }; +template +concept Pointer_Escaping_Read = requires(Field field) { + field.read([](const auto& value) { return &value; }); +}; +template +concept Wrapper_Escaping_Read = requires(Field field) { + field.read([](const auto& value) { return std::cref(value); }); +}; +template +concept View_Escaping_Read = requires(Field field) { + field.read([](const auto& value) { return std::span{&value, std::size_t{1}}; }); +}; +template +concept Reference_Escaping_Write = requires(Field field) { + field.write([](auto& value) -> auto& { return value; }); +}; } namespace adminive { template <> @@ -87,6 +105,10 @@ int main() { static_assert(Writable_Field); static_assert(Writable_Field); static_assert(!Reference_Escaping_Read); + static_assert(!Pointer_Escaping_Read); + static_assert(!Wrapper_Escaping_Read); + static_assert(!View_Escaping_Read); + static_assert(!Reference_Escaping_Write); const auto resolved = guarded.resolved_synchronization(); ADMINIVE_CHECK(resolved.lock_count == 2); ADMINIVE_CHECK(resolved.slot(0) != structive::Resolved_Synchronization_View::unsynchronized_slot); @@ -122,7 +144,7 @@ int main() { ADMINIVE_CHECK(Probe_Lock::exclusive_locks.load(std::memory_order_relaxed) == 0); ADMINIVE_CHECK(Probe_Lock::shared_locks.load(std::memory_order_relaxed) == 0); const auto descriptor = adminive::to_descriptor_json(); - ADMINIVE_CHECK(descriptor.at("protocol_version") == 5); + ADMINIVE_CHECK(descriptor.at("protocol_version") == adminive::resource_protocol_version); ADMINIVE_CHECK(!descriptor.at("fields").at(0).contains("lock_mode")); ADMINIVE_CHECK(descriptor.at("fields").at(0).at("writable") == true); ADMINIVE_CHECK(descriptor.at("fields").at(0).at("synchronized") == true); diff --git a/backend/service/tests/safety_test.cpp b/backend/service/tests/safety_test.cpp index abe4c19..f780af1 100644 --- a/backend/service/tests/safety_test.cpp +++ b/backend/service/tests/safety_test.cpp @@ -325,19 +325,7 @@ int main() { ADMINIVE_CHECK(update.success); ADMINIVE_CHECK(std::holds_alternative(devices.device)); ADMINIVE_CHECK(std::get(devices.device).token == "abc"); - bool invalid_name_rejected{}; - try { - static_cast(adminive::to_descriptor_json()); - } catch(const std::invalid_argument&) { - invalid_name_rejected = true; - } - ADMINIVE_CHECK(invalid_name_rejected); - bool conflict_rejected{}; - try { - static_cast(adminive::to_descriptor_json()); - } catch(const std::invalid_argument&) { - conflict_rejected = true; - } - ADMINIVE_CHECK(conflict_rejected); + ADMINIVE_CHECK_THROWS_AS((adminive::to_descriptor_json()), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS((adminive::to_descriptor_json()), std::invalid_argument); return 0; } diff --git a/backend/service/tests/test.cpp b/backend/service/tests/test.cpp index cc3135f..3ff45f7 100644 --- a/backend/service/tests/test.cpp +++ b/backend/service/tests/test.cpp @@ -1,25 +1,27 @@ #include "example_descriptors.hpp" #include "gallery.hpp" #include "adminive_test.hpp" +#include #include #include +#include #include namespace { using Json = adminive::example::Json; -const Json* find_named_node(const Json& value, std::string_view name) { +const Json* find_node(const Json& value, std::string_view property, std::string_view expected) { if(value.is_object()) { - if(value.contains("name") && value.at("name").is_string() && value.at("name").get() == name) { + if(value.contains(property) && value.at(property).is_string() && value.at(property).get() == expected) { return &value; } for(const auto& [key, child] : value.items()) { static_cast(key); - if(const Json* result = find_named_node(child, name)) { + if(const Json* result = find_node(child, property, expected)) { return result; } } } else if(value.is_array()) { for(const auto& child : value) { - if(const Json* result = find_named_node(child, name)) { + if(const Json* result = find_node(child, property, expected)) { return result; } } @@ -47,46 +49,6 @@ void assert_flex_items_renderable(const Json& value) { } } } -const Json* find_labeled_node(const Json& value, std::string_view label) { - if(value.is_object()) { - if(value.contains("label") && value.at("label").is_string() && value.at("label").get() == label) { - return &value; - } - for(const auto& [key, child] : value.items()) { - static_cast(key); - if(const Json* result = find_labeled_node(child, label)) { - return result; - } - } - } else if(value.is_array()) { - for(const auto& child : value) { - if(const Json* result = find_labeled_node(child, label)) { - return result; - } - } - } - return nullptr; -} -const Json* find_titled_node(const Json& value, std::string_view title) { - if(value.is_object()) { - if(value.contains("title") && value.at("title").is_string() && value.at("title").get() == title) { - return &value; - } - for(const auto& [key, child] : value.items()) { - static_cast(key); - if(const Json* result = find_titled_node(child, title)) { - return result; - } - } - } else if(value.is_array()) { - for(const auto& child : value) { - if(const Json* result = find_titled_node(child, title)) { - return result; - } - } - } - return nullptr; -} } int main() { using adminive::example::Alert_Config; @@ -96,8 +58,8 @@ int main() { using adminive::example::Radio_State; Radio_State state; const auto descriptor = adminive::to_descriptor_json(); - ADMINIVE_CHECK(descriptor.at("protocol") == "adminive.resource"); - ADMINIVE_CHECK(descriptor.at("protocol_version") == 5); + ADMINIVE_CHECK(descriptor.at("protocol").get() == adminive::resource_protocol); + ADMINIVE_CHECK(descriptor.at("protocol_version") == adminive::resource_protocol_version); ADMINIVE_CHECK(descriptor.at("name") == "radio_state"); ADMINIVE_CHECK(descriptor.at("fields").size() == 5); const auto& mode_field = descriptor.at("fields").at(0); @@ -180,7 +142,8 @@ int main() { const auto& create_form = crud_body.at("headerToolbar").at(2).at("dialog").at("body"); ADMINIVE_CHECK(create_form.at("actions").at(1).at("label") == "新增"); const auto status_descriptor = adminive::to_status_descriptor_json(); - ADMINIVE_CHECK(status_descriptor.at("protocol") == "adminive.status"); + ADMINIVE_CHECK(status_descriptor.at("protocol").get() == adminive::status_protocol); + ADMINIVE_CHECK(status_descriptor.at("protocol_version") == adminive::status_protocol_version); ADMINIVE_CHECK(status_descriptor.at("fields").size() == 3); ADMINIVE_CHECK(status_descriptor.at("fields").at(0).at("name") == "operating_state"); ADMINIVE_CHECK(status_descriptor.at("fields").at(0).at("value_type") == "enum"); @@ -224,11 +187,11 @@ int main() { ADMINIVE_CHECK(service_view_json.at("body").at("children").size() == 3); const auto service_form = adminive::to_amis_form_schema(service_config, service_view, "/admin/config/radio/data"); ADMINIVE_CHECK(service_form.at("body").at(0).at("type") == "tabs"); - const Json* profile_control = find_named_node(service_form, "profile_name"); - const Json* mode_control = find_named_node(service_form, "mode"); - const Json* worker_control = find_named_node(service_form, "worker_count"); - const Json* date_control = find_named_node(service_form, "appearance.effective_date"); - const Json* color_control = find_named_node(service_form, "appearance.accent_color"); + const Json* profile_control = find_node(service_form, "name", "profile_name"); + const Json* mode_control = find_node(service_form, "name", "mode"); + const Json* worker_control = find_node(service_form, "name", "worker_count"); + const Json* date_control = find_node(service_form, "name", "appearance.effective_date"); + const Json* color_control = find_node(service_form, "name", "appearance.accent_color"); ADMINIVE_CHECK(profile_control && profile_control->at("type") == "input-text"); ADMINIVE_CHECK(mode_control && mode_control->at("type") == "select"); ADMINIVE_CHECK(worker_control && worker_control->at("type") == "input-number"); @@ -245,10 +208,10 @@ int main() { const auto alert_view_json = adminive::to_view_json(alert_view); ADMINIVE_CHECK(alert_view_json.at("body").at("kind") == "vertical"); const auto alert_form = adminive::to_amis_form_schema(alert_config, alert_view, "/admin/config/alerts/data"); - ADMINIVE_CHECK(find_named_node(alert_form, "rule_name")); - ADMINIVE_CHECK(find_named_node(alert_form, "effective_date")->at("type") == "input-date"); - ADMINIVE_CHECK(find_named_node(alert_form, "highlight_color")->at("type") == "input-color"); - const Json* webhook_group = find_titled_node(alert_form, "Webhook 端点"); + ADMINIVE_CHECK(find_node(alert_form, "name", "rule_name")); + ADMINIVE_CHECK(find_node(alert_form, "name", "effective_date")->at("type") == "input-date"); + ADMINIVE_CHECK(find_node(alert_form, "name", "highlight_color")->at("type") == "input-color"); + const Json* webhook_group = find_node(alert_form, "title", "Webhook 端点"); ADMINIVE_CHECK(webhook_group); ADMINIVE_CHECK(webhook_group->at("visibleOn") == "${enabled && channel == 'webhook'}"); adminive::example::Http_Resource service_resource(service_config, "/admin/config/radio"); @@ -263,33 +226,26 @@ int main() { const auto gallery_schema = gallery.amis_schema(); ADMINIVE_CHECK(gallery_schema.at("type") == "panel"); assert_flex_items_renderable(gallery_schema); - const Json* automatic_control = find_named_node(gallery_schema, "automatic_text"); - const Json* text_control = find_named_node(gallery_schema, "text_value"); - const Json* multiline_control = find_named_node(gallery_schema, "multiline_value"); - const Json* number_control = find_named_node(gallery_schema, "number_value"); - const Json* even_control = find_named_node(gallery_schema, "even_number"); - const Json* boolean_control = find_named_node(gallery_schema, "boolean_value"); - const Json* select_control = find_named_node(gallery_schema, "select_value"); - const Json* date_gallery_control = find_named_node(gallery_schema, "date_value"); - const Json* color_gallery_control = find_named_node(gallery_schema, "color_value"); - const Json* optional_control = find_named_node(gallery_schema, "optional_text"); - const Json* readonly_control = find_named_node(gallery_schema, "read_only_text"); - ADMINIVE_CHECK(automatic_control && automatic_control->at("type") == "input-text"); - ADMINIVE_CHECK(text_control && text_control->at("type") == "input-text"); - ADMINIVE_CHECK(multiline_control && multiline_control->at("type") == "textarea"); - ADMINIVE_CHECK(number_control && number_control->at("type") == "input-number"); + constexpr std::array control_types{ + std::pair{"automatic_text", "input-text"}, std::pair{"text_value", "input-text"}, + std::pair{"multiline_value", "textarea"}, std::pair{"number_value", "input-number"}, + std::pair{"even_number", "input-number"}, std::pair{"boolean_value", "switch"}, + std::pair{"select_value", "select"}, std::pair{"date_value", "input-date"}, + std::pair{"color_value", "input-color"}, std::pair{"read_only_text", "static"}}; + for(const auto& [name, type] : control_types) { + const Json* control = find_node(gallery_schema, "name", name); + ADMINIVE_CHECK(control && control->at("type") == type); + } + const Json* number_control = find_node(gallery_schema, "name", "number_value"); ADMINIVE_CHECK(number_control->at("min") == 0 && number_control->at("max") == 100); - ADMINIVE_CHECK(even_control && even_control->at("type") == "input-number" && even_control->at("step") == 2); - ADMINIVE_CHECK(boolean_control && boolean_control->at("type") == "switch"); - ADMINIVE_CHECK(select_control && select_control->at("type") == "select"); - ADMINIVE_CHECK(date_gallery_control && date_gallery_control->at("type") == "input-date"); - ADMINIVE_CHECK(color_gallery_control && color_gallery_control->at("type") == "input-color"); + const Json* even_control = find_node(gallery_schema, "name", "even_number"); + ADMINIVE_CHECK(even_control->at("step") == 2); + const Json* optional_control = find_node(gallery_schema, "name", "optional_text"); ADMINIVE_CHECK(optional_control && optional_control->at("clearable") == true); - ADMINIVE_CHECK(readonly_control && readonly_control->at("type") == "static"); - const Json* save_gallery_button = find_labeled_node(gallery_schema, "保存画廊数据"); - const Json* create_gallery_button = find_labeled_node(gallery_schema, "新增一行"); - const Json* edit_gallery_button = find_labeled_node(gallery_schema, "编辑"); - const Json* delete_gallery_button = find_labeled_node(gallery_schema, "删除"); + const Json* save_gallery_button = find_node(gallery_schema, "label", "保存画廊数据"); + const Json* create_gallery_button = find_node(gallery_schema, "label", "新增一行"); + const Json* edit_gallery_button = find_node(gallery_schema, "label", "编辑"); + const Json* delete_gallery_button = find_node(gallery_schema, "label", "删除"); ADMINIVE_CHECK(save_gallery_button && save_gallery_button->at("type") == "submit"); ADMINIVE_CHECK(create_gallery_button && create_gallery_button->at("type") == "button"); ADMINIVE_CHECK(edit_gallery_button && edit_gallery_button->at("actionType") == "dialog"); diff --git a/backend/service/tests/view_schema_test.cpp b/backend/service/tests/view_schema_test.cpp index 5f45821..ec948fa 100644 --- a/backend/service/tests/view_schema_test.cpp +++ b/backend/service/tests/view_schema_test.cpp @@ -49,6 +49,8 @@ int main() { const auto form_view = adminive::describe_edit_view(); adminive::validate_form_view(form_view); const auto form_json = adminive::to_view_json(form_view); + ADMINIVE_CHECK(form_json.at("protocol").get() == adminive::view_protocol); + ADMINIVE_CHECK(form_json.at("protocol_version") == adminive::view_protocol_version); ADMINIVE_CHECK(form_json.at("kind") == "form"); ADMINIVE_CHECK(form_json.at("body").at("kind") == "vertical"); ADMINIVE_CHECK(form_json.at("body").at("children").at(0).at("kind") == "group"); @@ -115,7 +117,8 @@ int main() { ADMINIVE_CHECK(frontend_composition_amis.at("body").at(0).at("className") == "adminive-frontend-placement"); const std::vector slot_contracts{{"left", "amis_schema", "/left/descriptor", "/left/view", "/left/data", "/left/amis"}, {"right", "amis_schema", "/right/descriptor", "/right/view", "/right/data", "/right/amis"}}; const auto manifest = adminive::to_composition_manifest_json(frontend_composition, slot_contracts, {{"left", Json{{"type", "tpl"}, {"tpl", "left"}}}, {"right", Json{{"type", "tpl"}, {"tpl", "right"}}}}); - ADMINIVE_CHECK(manifest.at("protocol") == "adminive.composition-manifest"); + ADMINIVE_CHECK(manifest.at("protocol").get() == adminive::composition_manifest_protocol); + ADMINIVE_CHECK(manifest.at("protocol_version") == adminive::composition_manifest_protocol_version); ADMINIVE_CHECK(manifest.at("view").at("body").at("kind") == "frontend"); ADMINIVE_CHECK(manifest.at("slots").at("left").at("view_api") == "/left/view"); ADMINIVE_CHECK(manifest.at("slots").at("right").at("schema").at("tpl") == "right"); @@ -223,55 +226,15 @@ int main() { ADMINIVE_CHECK(response.status == 400); response = collection.reorder_response(R"({"ids":["invalid"]})"); ADMINIVE_CHECK(response.status == 400); - bool invalid_manifest{}; - try { - adminive::to_composition_manifest_json(frontend_composition, {adminive::Composition_Slot_Contract{"left", "amis_schema", {}, {}, {}, {}}}, {{"left", Json{{"type", "tpl"}}}}); - } catch(const std::invalid_argument&) { - invalid_manifest = true; - } - ADMINIVE_CHECK(invalid_manifest); - bool duplicate_manifest_slot{}; - try { + ADMINIVE_CHECK_THROWS_AS((adminive::to_composition_manifest_json(frontend_composition, {adminive::Composition_Slot_Contract{"left", "amis_schema", {}, {}, {}, {}}}, {{"left", Json{{"type", "tpl"}}}})), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS(([&] { const auto duplicate_frontend_composition = adminive::composition_view("duplicate", adminive::compose::frontend(adminive::compose::slot("left"), adminive::compose::slot("left"))); - adminive::to_composition_manifest_json(duplicate_frontend_composition, {slot_contracts.front()}, {{"left", Json{{"type", "tpl"}}}}); - } catch(const std::invalid_argument&) { - duplicate_manifest_slot = true; - } - ADMINIVE_CHECK(duplicate_manifest_slot); - bool invalid_grid{}; - try { - adminive::validate_form_view(adminive::edit_form(adminive::grid(0, adminive::use<&Config::name>()))); - } catch(const std::invalid_argument&) { - invalid_grid = true; - } - ADMINIVE_CHECK(invalid_grid); - bool invalid_tabs{}; - try { - adminive::validate_form_view(adminive::edit_form(adminive::tabs(adminive::use<&Config::name>()))); - } catch(const std::invalid_argument&) { - invalid_tabs = true; - } - ADMINIVE_CHECK(invalid_tabs); - bool invalid_tab_position{}; - try { - adminive::validate_form_view(adminive::edit_form(adminive::tab("Invalid", adminive::use<&Config::name>()))); - } catch(const std::invalid_argument&) { - invalid_tab_position = true; - } - ADMINIVE_CHECK(invalid_tab_position); - bool invalid_composition_tab_position{}; - try { - adminive::validate_composition_view(adminive::composition_view("invalid", adminive::compose::tab("Invalid", adminive::compose::slot("x")))); - } catch(const std::invalid_argument&) { - invalid_composition_tab_position = true; - } - ADMINIVE_CHECK(invalid_composition_tab_position); - bool invalid_sort{}; - try { - adminive::validate_table_view(adminive::table_view(adminive::column<&Config::name>()).default_sort("name")); - } catch(const std::invalid_argument&) { - invalid_sort = true; - } - ADMINIVE_CHECK(invalid_sort); + return adminive::to_composition_manifest_json(duplicate_frontend_composition, {slot_contracts.front()}, {{"left", Json{{"type", "tpl"}}}}); + }()), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS((adminive::validate_form_view(adminive::edit_form(adminive::grid(0, adminive::use<&Config::name>())))), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS((adminive::validate_form_view(adminive::edit_form(adminive::tabs(adminive::use<&Config::name>())))), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS((adminive::validate_form_view(adminive::edit_form(adminive::tab("Invalid", adminive::use<&Config::name>())))), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS((adminive::validate_composition_view(adminive::composition_view("invalid", adminive::compose::tab("Invalid", adminive::compose::slot("x"))))), std::invalid_argument); + ADMINIVE_CHECK_THROWS_AS((adminive::validate_table_view(adminive::table_view(adminive::column<&Config::name>()).default_sort("name"))), std::invalid_argument); return 0; } diff --git a/backend/test_support/adminive_test.hpp b/backend/test_support/adminive_test.hpp index 5c38261..85cf59e 100644 --- a/backend/test_support/adminive_test.hpp +++ b/backend/test_support/adminive_test.hpp @@ -1,6 +1,7 @@ #pragma once #include #include +#include namespace adminive::test { inline void check(bool condition, const char* expression, const char* file, int line) { if(condition) { @@ -9,5 +10,16 @@ inline void check(bool condition, const char* expression, const char* file, int std::cerr << file << ':' << line << ": check failed: " << expression << '\n'; std::abort(); } +template +bool throws_as(Function&& function) { + try { + std::forward(function)(); + } catch(const Exception&) { + return true; + } + return false; +} } #define ADMINIVE_CHECK(expression) ::adminive::test::check(static_cast(expression), #expression, __FILE__, __LINE__) +#define ADMINIVE_CHECK_THROWS_AS(expression, exception) \ + ADMINIVE_CHECK((::adminive::test::throws_as([&] { static_cast(expression); }))) diff --git a/cmake/PackageZip.cmake b/cmake/PackageZip.cmake index 3fec0d0..d554e11 100644 --- a/cmake/PackageZip.cmake +++ b/cmake/PackageZip.cmake @@ -46,13 +46,52 @@ if(PACKAGE_ZIP_RUN) list(REMOVE_ITEM package_files "${internal_relative}") endif() endforeach() + set(package_archive_root "${PACKAGE_ROOT}") + if(DEFINED PACKAGE_OVERLAY_ROOT AND NOT PACKAGE_OVERLAY_ROOT STREQUAL "") + if(NOT DEFINED PACKAGE_OVERLAY_PREFIX OR PACKAGE_OVERLAY_PREFIX STREQUAL "" OR PACKAGE_OVERLAY_PREFIX MATCHES "(^|/)\.\.(/|$)") + message(FATAL_ERROR "PACKAGE_OVERLAY_PREFIX must be a non-empty relative path") + endif() + file(STRINGS "${PACKAGE_OVERLAY_INCLUDE_FILE}" package_overlay_includes) + file(STRINGS "${PACKAGE_OVERLAY_EXCLUDE_FILE}" package_overlay_excludes) + set(package_overlay_files) + foreach(include_pattern IN LISTS package_overlay_includes) + package_collect_matches(matched_files "${PACKAGE_OVERLAY_ROOT}" "${include_pattern}") + list(APPEND package_overlay_files ${matched_files}) + endforeach() + list(REMOVE_DUPLICATES package_overlay_files) + set(package_overlay_excluded_files) + foreach(exclude_pattern IN LISTS package_overlay_excludes) + package_collect_matches(matched_files "${PACKAGE_OVERLAY_ROOT}" "${exclude_pattern}") + list(APPEND package_overlay_excluded_files ${matched_files}) + endforeach() + if(package_overlay_excluded_files) + list(REMOVE_DUPLICATES package_overlay_excluded_files) + list(REMOVE_ITEM package_overlay_files ${package_overlay_excluded_files}) + endif() + set(package_stage "${PACKAGE_FILE_LIST}.stage") + file(REMOVE_RECURSE "${package_stage}") + file(MAKE_DIRECTORY "${package_stage}") + foreach(relative_path IN LISTS package_files) + get_filename_component(destination_dir "${package_stage}/${relative_path}" DIRECTORY) + file(MAKE_DIRECTORY "${destination_dir}") + configure_file("${PACKAGE_ROOT}/${relative_path}" "${package_stage}/${relative_path}" COPYONLY) + endforeach() + foreach(relative_path IN LISTS package_overlay_files) + set(destination "${package_stage}/${PACKAGE_OVERLAY_PREFIX}/${relative_path}") + get_filename_component(destination_dir "${destination}" DIRECTORY) + file(MAKE_DIRECTORY "${destination_dir}") + configure_file("${PACKAGE_OVERLAY_ROOT}/${relative_path}" "${destination}" COPYONLY) + endforeach() + file(GLOB_RECURSE package_files LIST_DIRECTORIES false RELATIVE "${package_stage}" "${package_stage}/*") + set(package_archive_root "${package_stage}") + endif() list(SORT package_files) string(REPLACE ";" "\n" package_file_list_content "${package_files}") file(WRITE "${PACKAGE_FILE_LIST}" "${package_file_list_content}\n") get_filename_component(package_output_dir "${PACKAGE_OUTPUT_FILE}" DIRECTORY) file(MAKE_DIRECTORY "${package_output_dir}") file(REMOVE "${PACKAGE_OUTPUT_FILE}") - execute_process(COMMAND "${CMAKE_COMMAND}" -E tar cf "${PACKAGE_OUTPUT_FILE}" --format=zip "--files-from=${PACKAGE_FILE_LIST}" WORKING_DIRECTORY "${PACKAGE_ROOT}" RESULT_VARIABLE package_result) + execute_process(COMMAND "${CMAKE_COMMAND}" -E tar cf "${PACKAGE_OUTPUT_FILE}" --format=zip "--files-from=${PACKAGE_FILE_LIST}" WORKING_DIRECTORY "${package_archive_root}" RESULT_VARIABLE package_result) if(NOT package_result EQUAL 0) message(FATAL_ERROR "Failed to create ${PACKAGE_OUTPUT_FILE}") endif() @@ -76,8 +115,27 @@ function(add_project_zip_target target_name output_file root_directory include_l string(REPLACE ";" "\n" package_exclude_content "${${exclude_list_name}}") file(WRITE "${package_include_file}" "${package_include_content}\n") file(WRITE "${package_exclude_file}" "${package_exclude_content}\n") + set(package_overlay_arguments) + if(ARGC GREATER 5) + if(ARGC LESS 9) + message(FATAL_ERROR "Package overlay requires root, prefix, include-list name and exclude-list name") + endif() + get_filename_component(package_overlay_root "${ARGV5}" ABSOLUTE) + set(package_overlay_prefix "${ARGV6}") + set(package_overlay_include_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.overlay.includes") + set(package_overlay_exclude_file "${CMAKE_CURRENT_BINARY_DIR}/${target_name}.overlay.excludes") + string(REPLACE ";" "\n" package_overlay_include_content "${${ARGV7}}") + string(REPLACE ";" "\n" package_overlay_exclude_content "${${ARGV8}}") + file(WRITE "${package_overlay_include_file}" "${package_overlay_include_content}\n") + file(WRITE "${package_overlay_exclude_file}" "${package_overlay_exclude_content}\n") + list(APPEND package_overlay_arguments + "-DPACKAGE_OVERLAY_ROOT=${package_overlay_root}" + "-DPACKAGE_OVERLAY_PREFIX=${package_overlay_prefix}" + "-DPACKAGE_OVERLAY_INCLUDE_FILE=${package_overlay_include_file}" + "-DPACKAGE_OVERLAY_EXCLUDE_FILE=${package_overlay_exclude_file}") + endif() add_custom_target(${target_name} - COMMAND "${CMAKE_COMMAND}" -DPACKAGE_ZIP_RUN=ON "-DPACKAGE_ROOT=${package_root}" "-DPACKAGE_OUTPUT_FILE=${package_output_file}" "-DPACKAGE_FILE_LIST=${package_file_list}" "-DPACKAGE_INCLUDE_FILE=${package_include_file}" "-DPACKAGE_EXCLUDE_FILE=${package_exclude_file}" -P "${CMAKE_CURRENT_FUNCTION_LIST_FILE}" + COMMAND "${CMAKE_COMMAND}" -DPACKAGE_ZIP_RUN=ON "-DPACKAGE_ROOT=${package_root}" "-DPACKAGE_OUTPUT_FILE=${package_output_file}" "-DPACKAGE_FILE_LIST=${package_file_list}" "-DPACKAGE_INCLUDE_FILE=${package_include_file}" "-DPACKAGE_EXCLUDE_FILE=${package_exclude_file}" ${package_overlay_arguments} -P "${CMAKE_CURRENT_FUNCTION_LIST_FILE}" BYPRODUCTS "${package_output_file}" COMMENT "Creating ${package_output_file}" VERBATIM diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5f5e6a0..8fe9ddd 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "dependencies": { "amis": "6.13.0", - "axios": "1.7.9", + "axios": "1.19.0", "mobx": "4.15.7", "mobx-react": "6.3.1", "mobx-react-lite": "2.2.2", @@ -18,12 +18,12 @@ "react-dom": "18.3.1" }, "devDependencies": { + "@playwright/test": "1.62.0", "@types/react": "18.3.18", "@types/react-dom": "18.3.5", "@vitejs/plugin-react": "4.3.4", "typescript": "5.7.3", - "vite": "6.1.0", - "@playwright/test": "1.62.0" + "vite": "6.1.0" } }, "node_modules/@babel/code-frame": { @@ -780,6 +780,19 @@ "react": "*" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", + "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -884,6 +897,20 @@ "node": "^22.20 || ^24.12 || >=25" } }, + "node_modules/@playwright/test": { + "version": "1.62.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.62.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -1477,7 +1504,6 @@ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", - "optional": true, "dependencies": { "debug": "4" }, @@ -1779,14 +1805,15 @@ } }, "node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { @@ -2048,13 +2075,13 @@ } }, "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "license": "ISC", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", + "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "license": "BlueOak-1.0.0", "optional": true, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/classnames": { @@ -2244,7 +2271,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2719,39 +2745,6 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs-minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3012,7 +3005,6 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", - "optional": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -3550,60 +3542,26 @@ } }, "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "license": "ISC", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "license": "BlueOak-1.0.0", "optional": true, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", + "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", "optional": true, "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "license": "ISC", - "optional": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "optional": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" + "node": ">= 18" } }, "node_modules/mobx": { @@ -3704,7 +3662,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, "license": "MIT" }, "node_modules/nan": { @@ -3906,6 +3863,31 @@ "dev": true, "license": "ISC" }, + "node_modules/playwright": { + "version": "1.62.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.62.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright-core": { + "version": "1.62.0", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/postcss": { "version": "8.5.25", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", @@ -3968,10 +3950,13 @@ "license": "MIT" }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pure-color": { "version": "1.3.0", @@ -4709,22 +4694,20 @@ } }, "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "license": "ISC", + "version": "7.5.22", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.22.tgz", + "integrity": "sha512-MFO/QzvtAOmJbkhOaCTvbGcFN9L9b+JunIsDwaKljSOdcLMea3NJ1k9Usz/rjdfSXTq4dfzfeS7W4p4YOAAHeA==", + "license": "BlueOak-1.0.0", "optional": true, "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, "node_modules/tar-stream": { @@ -4744,11 +4727,14 @@ } }, "node_modules/tar/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC", - "optional": true + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", + "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", + "license": "BlueOak-1.0.0", + "optional": true, + "engines": { + "node": ">=18" + } }, "node_modules/tiny-invariant": { "version": "1.3.3", @@ -5245,45 +5231,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "license": "0BSD" - }, - "node_modules/@playwright/test": { - "version": "1.62.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.62.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright": { - "version": "1.62.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.62.0" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright-core": { - "version": "1.62.0", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } } } } diff --git a/frontend/package.json b/frontend/package.json index 9742d1b..4c4bc90 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "amis": "6.13.0", - "axios": "1.7.9", + "axios": "1.19.0", "mobx": "4.15.7", "mobx-react": "6.3.1", "mobx-react-lite": "2.2.2", @@ -34,6 +34,7 @@ "mobx": "4.15.7", "mobx-react": "6.3.1", "mobx-react-lite": "2.2.2", - "mobx-state-tree": "3.17.3" + "mobx-state-tree": "3.17.3", + "tar": "7.5.22" } }