Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 31 additions & 10 deletions .github/workflows/archlinux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,21 @@ jobs:
-DCMAKE_CXX_STANDARD=23 \
-DJSON_Diagnostics=ON \
-DNUI_FETCH_TRAITS=OFF \
-DNUI_SFTP_ENABLE_TESTING=ON \

- name: Build
run: |
cmake --build \
"${{ env.BUILD_DIR }}" \
--config "${{ env.BUILD_TYPE }}"

- name: Run Tests
run: |
ctest \
--test-dir "${{ env.BUILD_DIR }}" \
--build-config "${{ env.BUILD_TYPE }}" \
--output-on-failure

- name: Run Deploy
run: |
bash scripts/deploy.sh
Expand All @@ -99,27 +107,40 @@ jobs:
run: |
cd "${{ env.BUILD_DIR }}" && tar -czf "nui-sftp-linux-frontend_${{ env.VERSION }}.tar.gz" frontend

- name: Build standalone licenses tarball
run: |
# The cmake build already produced licenses.json (and embedded it
# into the WASM binary). This step only repackages it as a release
# artifact for inspection outside the app.
python scripts/build_licenses_bundle.py \
--out "${{ env.BUILD_DIR }}/licenses.json" \
--tarball "${{ env.BUILD_DIR }}/nui-sftp-licenses_${{ env.VERSION }}.tar.gz" \
--npm-licenses "${{ env.BUILD_DIR }}/generated/licenses/npm-licenses.json"

- name: Upload Artifact to Release
if: startsWith(github.ref, 'refs/tags/v')
env:
GH_TOKEN: ${{ secrets.RELEASEUPLOADPAT }}
run: |
gh release upload "v${{ env.VERSION_CLEAN }}" "${{ env.BUILD_DIR }}/nui-sftp-linux-frontend_${{ env.VERSION }}.tar.gz"
gh release upload "v${{ env.VERSION_CLEAN }}" "${{ env.BUILD_DIR }}/nui-sftp-licenses_${{ env.VERSION }}.tar.gz"

- name: Upload artifacts to MEGA S4
if: startsWith(github.ref, 'refs/tags/v')
env:
AWS_ACCESS_KEY_ID: ${{ secrets.MEGAS4ACCESS }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.MEGAS4KEY }}
ENDPOINT: https://s3.eu-central-1.s4.mega.io
FILENAME: "nui-sftp-linux-frontend_${{ env.VERSION }}.tar.gz"
S3_PATH: "s3://nui-sftp-releases/linux-frontend/"
run: |
# Check if the file exists on the remote
if aws s3 ls "${{ env.S3_PATH }}${{ env.FILENAME }}" --endpoint-url ${{ env.ENDPOINT }} > /dev/null 2>&1; then
echo "Error: File ${{ env.FILENAME }} already exists in S4. Exiting to prevent overwrite."
exit 0
fi

# If it doesnt exist, upload it
aws s3 cp "${{ env.BUILD_DIR }}/${{ env.FILENAME }}" "${{ env.S3_PATH }}" --endpoint-url ${{ env.ENDPOINT }}
upload_if_absent() {
local filename="$1"
local s3_path="$2"
if aws s3 ls "${s3_path}${filename}" --endpoint-url "${ENDPOINT}" > /dev/null 2>&1; then
echo "Skipping: ${filename} already exists in S4."
return 0
fi
aws s3 cp "${{ env.BUILD_DIR }}/${filename}" "${s3_path}" --endpoint-url "${ENDPOINT}"
}

upload_if_absent "nui-sftp-linux-frontend_${{ env.VERSION }}.tar.gz" "s3://nui-sftp-releases/linux-frontend/"
upload_if_absent "nui-sftp-licenses_${{ env.VERSION }}.tar.gz" "s3://nui-sftp-releases/licenses/"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ todo.txt
.clangd
install
.claude
.mcp.json
.mcp.json
licenses/npm-licenses.json
17 changes: 17 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ add_subdirectory("${CMAKE_SOURCE_DIR}/dependencies/Nui" EXCLUDE_FROM_ALL)
add_executable(${PROJECT_NAME})
target_link_libraries(${PROJECT_NAME} PUBLIC core-target)

# Third-party license drift check: fails the build if a CMake-declared dep
# is missing from licenses/third_party.spdx.json (or vice versa) or any
# referenced license text file is empty / still a PLACEHOLDER.
find_package(Python3 COMPONENTS Interpreter QUIET)
if (Python3_Interpreter_FOUND)
add_custom_target(check_licenses
ALL
COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/scripts/check_licenses.py"
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
COMMENT "Checking third-party license manifest"
VERBATIM
)
add_dependencies(${PROJECT_NAME} check_licenses)
else()
message(WARNING "Python3 interpreter not found; skipping license drift check.")
endif()

# Subdirectories & Dependencies
add_library(roar-include-only INTERFACE)
target_include_directories(roar-include-only INTERFACE "${CMAKE_CURRENT_LIST_DIR}/dependencies/roar/include")
Expand Down
1 change: 1 addition & 0 deletions frontend/include/frontend/events/frontend_events.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ struct FrontendEvents : public AppWideEvents
Nui::Observed<std::string> onNewSession{};
Nui::Observed<bool> onLayoutsChanged{false};
Nui::Observed<bool> settingsOpen{false};
Nui::Observed<bool> licensesOpen{false};
/// Opens settings and scrolls to the rendered element whose DOM id equals
/// this string. Settings walks up from the element to find its
/// [data-settings-section] ancestor and activates that section first, so
Expand Down
25 changes: 25 additions & 0 deletions frontend/include/frontend/licenses.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#pragma once

#include <frontend/events/frontend_events.hpp>

#include <nui/frontend/element_renderer.hpp>

#include <roar/detail/pimpl_special_functions.hpp>

class Licenses
{
public:
explicit Licenses(FrontendEvents* events);
ROAR_PIMPL_SPECIAL_FUNCTIONS(Licenses);

Nui::ElementRenderer operator()();

private:
void loadIfNeeded();
Nui::ElementRenderer header();
Nui::ElementRenderer sidebar();
Nui::ElementRenderer main();

struct Implementation;
std::unique_ptr<Implementation> impl_;
};
86 changes: 86 additions & 0 deletions frontend/source/frontend/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,81 @@ add_npm_install(
INSTALL_ARGS ${NUISCP_NPM_INSTALL_ARGS}
)

# ── Embedded license bundle ────────────────────────────────────────────────
# Generates licenses_data.hpp at build time and embeds the merged C++ + npm
# license bundle as a std::string_view, so the WASM frontend can render the
# Licenses page without any runtime file lookup.

set(NUI_SFTP_LICENSES_GEN_DIR "${CMAKE_BINARY_DIR}/generated/licenses")
set(NUI_SFTP_NPM_LICENSES_JSON "${NUI_SFTP_LICENSES_GEN_DIR}/npm-licenses.json")
set(NUI_SFTP_LICENSES_JSON "${NUI_SFTP_LICENSES_GEN_DIR}/licenses.json")
set(NUI_SFTP_LICENSES_HEADER "${NUI_SFTP_LICENSES_GEN_DIR}/licenses_data.hpp")

find_package(Python3 COMPONENTS Interpreter REQUIRED)

# Glob the per-package license texts so the header re-generates whenever any
# of them is edited.
file(GLOB NUI_SFTP_LICENSE_TEXT_FILES
CONFIGURE_DEPENDS
"${CMAKE_SOURCE_DIR}/licenses/texts/*"
)

# Step 1: walk the build-tree node_modules and dump per-package license info.
add_custom_command(
OUTPUT "${NUI_SFTP_NPM_LICENSES_JSON}"
COMMAND ${CMAKE_COMMAND} -E make_directory "${NUI_SFTP_LICENSES_GEN_DIR}"
COMMAND "${CMAKE_BINARY_DIR}/node_modules/.bin/license-checker-rseidelsohn"
--start "${CMAKE_BINARY_DIR}"
--production
--json
--out "${NUI_SFTP_NPM_LICENSES_JSON}"
DEPENDS ${PROJECT_NAME}-npm-install
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
COMMENT "Collecting npm production licenses"
VERBATIM
)

# Step 2: merge SPDX + npm output into the embedded C++ header. Re-runs when
# any source license input changes.
add_custom_command(
OUTPUT "${NUI_SFTP_LICENSES_HEADER}" "${NUI_SFTP_LICENSES_JSON}"
COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/scripts/build_licenses_bundle.py"
--out "${NUI_SFTP_LICENSES_JSON}"
--header "${NUI_SFTP_LICENSES_HEADER}"
--npm-licenses "${NUI_SFTP_NPM_LICENSES_JSON}"
--npm-host-root "${CMAKE_BINARY_DIR}"
DEPENDS
"${NUI_SFTP_NPM_LICENSES_JSON}"
"${CMAKE_SOURCE_DIR}/scripts/build_licenses_bundle.py"
"${CMAKE_SOURCE_DIR}/licenses/third_party.spdx.json"
${NUI_SFTP_LICENSE_TEXT_FILES}
COMMENT "Generating embedded license header"
VERBATIM
)

add_custom_target(nui-sftp-licenses-data
DEPENDS "${NUI_SFTP_LICENSES_HEADER}"
)

# Ensure the embedded header exists at configure time, so source files that
# `#include <licenses_data.hpp>` resolve before the build-time custom command
# has run. Whatever is here will be overwritten on the first build with the
# full cpp+npm bundle once npm install has produced a node_modules.
if (NOT EXISTS "${NUI_SFTP_LICENSES_HEADER}")
file(MAKE_DIRECTORY "${NUI_SFTP_LICENSES_GEN_DIR}")
execute_process(
COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/scripts/build_licenses_bundle.py"
--out "${NUI_SFTP_LICENSES_JSON}"
--header "${NUI_SFTP_LICENSES_HEADER}"
--npm-licenses "${NUI_SFTP_NPM_LICENSES_JSON}"
--npm-host-root "${CMAKE_BINARY_DIR}"
RESULT_VARIABLE NUI_SFTP_LICENSES_BOOTSTRAP_RESULT
)
if (NOT NUI_SFTP_LICENSES_BOOTSTRAP_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to bootstrap licenses_data.hpp at configure time")
endif()
endif()

target_sources(
${PROJECT_NAME}
PRIVATE
Expand All @@ -18,6 +93,7 @@ target_sources(
session.cpp
proto_session.cpp
settings.cpp
licenses.cpp
color.cpp
icon_from_name.cpp
theme_controller.cpp
Expand Down Expand Up @@ -69,8 +145,17 @@ target_sources(
file_explorer/remote_side_model.cpp
file_explorer/local_side_model.cpp
"${CMAKE_BINARY_DIR}/include/build_environment.hpp"
"${NUI_SFTP_LICENSES_HEADER}"
)

target_include_directories(${PROJECT_NAME} PRIVATE "${NUI_SFTP_LICENSES_GEN_DIR}")
add_dependencies(${PROJECT_NAME} nui-sftp-licenses-data)
# Nui's inline preprocessor compiles every .cpp into a .i before the WASM
# target builds, so it also needs the generated header to exist first.
if (TARGET nui-inline-${PROJECT_NAME})
add_dependencies(nui-inline-${PROJECT_NAME} nui-sftp-licenses-data)
endif()

function(set_memory64_flags)
cmake_parse_arguments(
MEM64
Expand Down Expand Up @@ -161,6 +246,7 @@ nui_prepare_emscripten_target(
"${CMAKE_SOURCE_DIR}/static/styles/session.css"
"${CMAKE_SOURCE_DIR}/static/styles/settings_group.css"
"${CMAKE_SOURCE_DIR}/static/styles/settings.css"
"${CMAKE_SOURCE_DIR}/static/styles/licenses.css"
"${CMAKE_SOURCE_DIR}/static/styles/sync_dialog.css"
"${CMAKE_SOURCE_DIR}/static/styles/sidebar.css"
"${CMAKE_SOURCE_DIR}/static/styles/toolbar.css"
Expand Down
Loading