From 47a4072561daaa25a8944a57b36b5186406ae3e8 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:41:04 +0200 Subject: [PATCH 1/8] feat(Makefile): add generate_apps_matrix_json with dynamic app list Adds the generate_apps_matrix_json target that emits the CI build matrix from the canonical app-list variables, then refactors the target to use the dynamic app-list pattern shared with the rest of the Makefile so the matrix never drifts from the build inputs. --- Makefile | 53 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index deaac8e..d7a0f1a 100644 --- a/Makefile +++ b/Makefile @@ -6,18 +6,28 @@ # Build configuration TARGET_PACKAGE_NAME = hidrivenext-server.zip +# App category lists — drive .build_deps and generate_apps_matrix_json +# apps-custom/ — npm only (no composer) +CUSTOM_NPM_APPS = simplesettings +# apps-custom/ — composer only (no npm, even if package.json present) +CUSTOM_COMPOSER_APPS = nc_ionos_processes nc_theming +# apps-external/ — full build (composer + npm) +EXTERNAL_FULL_APPS = richdocuments user_oidc viewer +# themes/ — npm only; lock file lives under ${path}/IONOS/ +THEME_APPS = nc-ionos-theme + # Core build targets .PHONY: help clean # Main Nextcloud build .PHONY: build_nextcloud build_nextcloud_only -# Applications -.PHONY: build_dep_simplesettings_app build_dep_nc_ionos_processes_app build_dep_user_oidc_app build_dep_viewer_app build_richdocuments_app build_dep_theming_app -# Themes -.PHONY: build_dep_ionos_theme +# Applications and themes — dynamically derived from category lists +.PHONY: $(patsubst %,build_%_app,$(CUSTOM_NPM_APPS) $(CUSTOM_COMPOSER_APPS) $(EXTERNAL_FULL_APPS) $(THEME_APPS)) # Configuration and packaging .PHONY: add_config_partials patch_shipped_json version.json zip_dependencies # Meta targets .PHONY: .build_deps build_release build_locally +# CI matrix generation +.PHONY: generate_apps_matrix_json # HELP # This will output the help for each task @@ -53,22 +63,22 @@ build_nextcloud: build_nextcloud_only ## Build HiDrive Next dev_nextcloud: build_nextcloud_dev ## Build HiDrive Next (dev) @echo "[i] HiDrive Next built" -build_dep_simplesettings_app: ## Install and build simplesettings app +build_simplesettings_app: ## Install and build simplesettings app cd apps-custom/simplesettings && \ npm ci && \ npm run build -build_dep_nc_ionos_processes_app: ## Install nc_ionos_processes app +build_nc_ionos_processes_app: ## Install nc_ionos_processes app cd apps-custom/nc_ionos_processes && \ composer install --no-dev -o -build_dep_user_oidc_app: ## Install and build user_oidc app +build_user_oidc_app: ## Install and build user_oidc app cd apps-external/user_oidc && \ composer install --no-dev -o && \ npm ci && \ npm run build -build_dep_viewer_app: ## Install and build viewer app +build_viewer_app: ## Install and build viewer app cd apps-external/viewer && \ composer install --no-dev -o && \ npm ci && \ @@ -80,12 +90,12 @@ build_richdocuments_app: ## Install and build richdocuments viewer app npm ci && \ npm run build -build_dep_ionos_theme: ## Install and build ionos theme +build_nc-ionos-theme_app: ## Install and build ionos theme cd themes/nc-ionos-theme/IONOS && \ npm ci && \ npm run build -build_dep_theming_app: ## Build the custom css +build_nc_theming_app: ## Build the custom css cd apps-custom/nc_theming && \ make build_css @@ -167,10 +177,31 @@ zip_dependencies: patch_shipped_json version.json ## Zip relevant files -x "themes/nc-ionos-theme/README.md" \ -x "themes/nc-ionos-theme/IONOS**" -.build_deps: build_dep_viewer_app build_richdocuments_app build_dep_simplesettings_app build_dep_nc_ionos_processes_app build_dep_user_oidc_app build_dep_ionos_theme build_dep_theming_app +.build_deps: $(patsubst %,build_%_app,$(CUSTOM_NPM_APPS) $(CUSTOM_COMPOSER_APPS) $(EXTERNAL_FULL_APPS) $(THEME_APPS)) build_release: build_nextcloud .build_deps add_config_partials zip_dependencies ## Build a release package (build apps/themes, copy configs and package) @echo "[i] Everything done for a release" build_locally: dev_nextcloud .build_deps ## Build all apps/themes for local development @echo "[i] Everything done for local/dev" + +generate_apps_matrix_json: ## Generate JSON matrix of buildable apps for the CI pipeline + @bash -c ' \ + emit() { \ + local app="$$1" path="$$2" has_npm="$$3" has_composer="$$4"; \ + local npm_lock_path=""; \ + if [ "$$has_npm" = "true" ]; then \ + if [ -f "$$path/IONOS/package-lock.json" ]; then \ + npm_lock_path="$$path/IONOS/package-lock.json"; \ + else \ + npm_lock_path="$$path/package-lock.json"; \ + fi; \ + fi; \ + printf "{\"name\":\"%s\",\"path\":\"%s\",\"has_npm\":%s,\"has_composer\":%s,\"npm_lock_path\":\"%s\",\"makefile_target\":\"build_%s_app\",\"needs_custom_npms\":false}\n" \ + "$$app" "$$path" "$$has_npm" "$$has_composer" "$$npm_lock_path" "$$app"; \ + }; \ + for app in $(CUSTOM_NPM_APPS); do emit "$$app" "apps-custom/$$app" true false; done; \ + for app in $(CUSTOM_COMPOSER_APPS); do emit "$$app" "apps-custom/$$app" false true; done; \ + for app in $(EXTERNAL_FULL_APPS); do emit "$$app" "apps-external/$$app" true true; done; \ + for app in $(THEME_APPS); do emit "$$app" "themes/$$app" true false; done \ + ' | jq -s 'sort_by(.name)' From aef6356e41b878dc22987d7bc3630986595cc271 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:44:56 +0200 Subject: [PATCH 2/8] feat(Makefile): introduce dynamic build rules via define macros + common vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces hand-written per-app build recipes with define macros: - build_custom_npm_app / build_custom_composer_app / build_external_full_app generate dynamic %-suffix rules over CUSTOM_NPM_TARGETS, CUSTOM_COMPOSER_TARGETS and EXTERNAL_FULL_TARGETS. - Verbose [i]/[✓] log output is wired into every recipe so the CI log reads consistently across categories. - `make help` lists targets category-by-category and reflects the dynamic app set. - build_nc_theming_app uses $(MAKE) for the recursive build so flags and jobserver state propagate. --- Makefile | 146 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 98 insertions(+), 48 deletions(-) diff --git a/Makefile b/Makefile index d7a0f1a..1e10f17 100644 --- a/Makefile +++ b/Makefile @@ -6,22 +6,40 @@ # Build configuration TARGET_PACKAGE_NAME = hidrivenext-server.zip +# Common build commands +COMPOSER_INSTALL = composer install --no-dev -o --no-interaction +NPM_INSTALL = npm ci --prefer-offline --no-audit +NPM_BUILD = npm run build + # App category lists — drive .build_deps and generate_apps_matrix_json # apps-custom/ — npm only (no composer) CUSTOM_NPM_APPS = simplesettings # apps-custom/ — composer only (no npm, even if package.json present) -CUSTOM_COMPOSER_APPS = nc_ionos_processes nc_theming +CUSTOM_COMPOSER_APPS = nc_ionos_processes # apps-external/ — full build (composer + npm) EXTERNAL_FULL_APPS = richdocuments user_oidc viewer -# themes/ — npm only; lock file lives under ${path}/IONOS/ -THEME_APPS = nc-ionos-theme +# Apps with special build targets (not in the standard categories above) +# These apps have dedicated build__app targets with custom build logic +SPECIAL_BUILD_APPS = nc_theming nc-ionos-theme + +# Metadata for generate_apps_matrix_json: "name|path|has_npm|has_composer" +# One entry per app in SPECIAL_BUILD_APPS — must be kept in sync. +SPECIAL_BUILD_APPS_META = \ + "nc_theming|apps-custom/nc_theming|false|true" \ + "nc-ionos-theme|themes/nc-ionos-theme|true|false" + +# Generate build target lists dynamically from category lists +CUSTOM_NPM_TARGETS = $(patsubst %,build_%_app,$(CUSTOM_NPM_APPS)) +CUSTOM_COMPOSER_TARGETS = $(patsubst %,build_%_app,$(CUSTOM_COMPOSER_APPS)) +EXTERNAL_FULL_TARGETS = $(patsubst %,build_%_app,$(EXTERNAL_FULL_APPS)) +SPECIAL_BUILD_TARGETS = $(patsubst %,build_%_app,$(SPECIAL_BUILD_APPS)) # Core build targets .PHONY: help clean # Main Nextcloud build -.PHONY: build_nextcloud build_nextcloud_only -# Applications and themes — dynamically derived from category lists -.PHONY: $(patsubst %,build_%_app,$(CUSTOM_NPM_APPS) $(CUSTOM_COMPOSER_APPS) $(EXTERNAL_FULL_APPS) $(THEME_APPS)) +.PHONY: build_nextcloud build_nextcloud_only build_nextcloud_dev dev_nextcloud +# Applications — dynamically derived from category lists +.PHONY: $(CUSTOM_NPM_TARGETS) $(CUSTOM_COMPOSER_TARGETS) $(EXTERNAL_FULL_TARGETS) $(SPECIAL_BUILD_TARGETS) # Configuration and packaging .PHONY: add_config_partials patch_shipped_json version.json zip_dependencies # Meta targets @@ -38,6 +56,12 @@ help: ## This help. @echo "Usage: make [target]" @echo "" @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) + @echo "" + @echo "Individual app build targets:" + @for app in $(CUSTOM_NPM_APPS); do printf " \033[36m%-35s\033[0m %s\n" "build_$${app}_app" "apps-custom npm"; done + @for app in $(CUSTOM_COMPOSER_APPS); do printf " \033[36m%-35s\033[0m %s\n" "build_$${app}_app" "apps-custom composer"; done + @for app in $(EXTERNAL_FULL_APPS); do printf " \033[36m%-35s\033[0m %s\n" "build_$${app}_app" "apps-external composer+npm"; done + @for app in $(SPECIAL_BUILD_APPS); do printf " \033[36m%-35s\033[0m %s\n" "build_$${app}_app" "special"; done clean: ## Clean up build artifacts @echo "[i] Cleaning build artifacts..." @@ -45,63 +69,74 @@ clean: ## Clean up build artifacts rm -f version.json rm -f $(TARGET_PACKAGE_NAME) -build_nextcloud_only: ## Build HiDrive Next only (no custom npm packages rebuild) +build_nextcloud_only: ## Build HiDrive Next only (no custom npm packages rebuild) set -e && \ - composer install --no-dev -o && \ - npm ci && \ - NODE_OPTIONS="--max-old-space-size=4096" npm run build + $(COMPOSER_INSTALL) && \ + $(NPM_INSTALL) && \ + NODE_OPTIONS="--max-old-space-size=4096" $(NPM_BUILD) + @echo "[✓] HiDrive Next core built successfully" -build_nextcloud_dev: ## Build HiDrive Next only (no custom npm packages rebuild) +build_nextcloud_dev: ## Build HiDrive Next dev (no custom npm packages rebuild) set -e && \ - composer install --no-dev -o && \ - npm ci && \ + $(COMPOSER_INSTALL) && \ + $(NPM_INSTALL) && \ NODE_OPTIONS="--max-old-space-size=4096" npm run dev + @echo "[✓] HiDrive Next core (dev) built successfully" build_nextcloud: build_nextcloud_only ## Build HiDrive Next @echo "[i] HiDrive Next built" -dev_nextcloud: build_nextcloud_dev ## Build HiDrive Next (dev) +dev_nextcloud: build_nextcloud_dev ## Build HiDrive Next dev @echo "[i] HiDrive Next built" -build_simplesettings_app: ## Install and build simplesettings app - cd apps-custom/simplesettings && \ - npm ci && \ - npm run build - -build_nc_ionos_processes_app: ## Install nc_ionos_processes app - cd apps-custom/nc_ionos_processes && \ - composer install --no-dev -o - -build_user_oidc_app: ## Install and build user_oidc app - cd apps-external/user_oidc && \ - composer install --no-dev -o && \ - npm ci && \ - npm run build - -build_viewer_app: ## Install and build viewer app - cd apps-external/viewer && \ - composer install --no-dev -o && \ - npm ci && \ - npm run build - -build_richdocuments_app: ## Install and build richdocuments viewer app - cd apps-external/richdocuments && \ - composer install --no-dev -o && \ - npm ci && \ - npm run build +# Common macros for standard build categories +define build_custom_npm_app + @echo "[i] Building $(1) app..." + @cd apps-custom/$(1) && $(NPM_INSTALL) && $(NPM_BUILD) + @echo "[✓] $(1) app built successfully" +endef -build_nc-ionos-theme_app: ## Install and build ionos theme - cd themes/nc-ionos-theme/IONOS && \ - npm ci && \ - npm run build +define build_custom_composer_app + @echo "[i] Building $(1) app..." + @cd apps-custom/$(1) && $(COMPOSER_INSTALL) + @echo "[✓] $(1) app built successfully" +endef + +define build_external_full_app + @echo "[i] Building $(1) app..." + @cd apps-external/$(1) && $(COMPOSER_INSTALL) && $(NPM_INSTALL) && $(NPM_BUILD) + @echo "[✓] $(1) app built successfully" +endef + +# Dynamic rules for standard categories +$(CUSTOM_NPM_TARGETS): build_%_app: + $(call build_custom_npm_app,$(patsubst build_%_app,%,$@)) + +$(CUSTOM_COMPOSER_TARGETS): build_%_app: + $(call build_custom_composer_app,$(patsubst build_%_app,%,$@)) + +$(EXTERNAL_FULL_TARGETS): build_%_app: + $(call build_external_full_app,$(patsubst build_%_app,%,$@)) + +# Special build targets — custom logic that doesn't fit the standard categories build_nc_theming_app: ## Build the custom css + @echo "[i] Building nc_theming app..." cd apps-custom/nc_theming && \ - make build_css + $(MAKE) build_css + @echo "[✓] nc_theming app built successfully" + +build_nc-ionos-theme_app: ## Install and build ionos theme + @echo "[i] Building nc-ionos-theme app..." + cd themes/nc-ionos-theme/IONOS && \ + $(NPM_INSTALL) && \ + $(NPM_BUILD) + @echo "[✓] nc-ionos-theme app built successfully" add_config_partials: ## Copy custom config files to Nextcloud config @echo "[i] Copying config files..." cp IONOS/configs/*.config.php config/ + @echo "[✓] Config files copied successfully" patch_shipped_json: ## Patch shipped.json to make core apps disableable @echo "[i] Patching shipped.json..." @@ -117,10 +152,19 @@ version.json: ## Generate version file jq . version.json zip_dependencies: patch_shipped_json version.json ## Zip relevant files + @echo "[i] Checking if .buildnumber exists..." @if [ ! -f .buildnumber ]; then \ - echo "Error: .buildnumber file is missing. Inject it before packaging (e.g. echo 42 > .buildnumber)"; \ + echo ""; \ + echo "**********************************************************************"; \ + echo "ERROR: .buildnumber file not found!"; \ + echo ""; \ + echo "The .buildnumber file must exist before creating the package."; \ + echo "Inject it before packaging, e.g. echo 42 > .buildnumber"; \ + echo "**********************************************************************"; \ + echo ""; \ exit 1; \ fi + @echo "[i] .buildnumber found: $$(cat .buildnumber)" @echo "[i] zip relevant files to $(TARGET_PACKAGE_NAME)" && \ zip -r "$(TARGET_PACKAGE_NAME)" \ .buildnumber \ @@ -177,7 +221,7 @@ zip_dependencies: patch_shipped_json version.json ## Zip relevant files -x "themes/nc-ionos-theme/README.md" \ -x "themes/nc-ionos-theme/IONOS**" -.build_deps: $(patsubst %,build_%_app,$(CUSTOM_NPM_APPS) $(CUSTOM_COMPOSER_APPS) $(EXTERNAL_FULL_APPS) $(THEME_APPS)) +.build_deps: $(CUSTOM_NPM_TARGETS) $(CUSTOM_COMPOSER_TARGETS) $(EXTERNAL_FULL_TARGETS) $(SPECIAL_BUILD_TARGETS) build_release: build_nextcloud .build_deps add_config_partials zip_dependencies ## Build a release package (build apps/themes, copy configs and package) @echo "[i] Everything done for a release" @@ -203,5 +247,11 @@ generate_apps_matrix_json: ## Generate JSON matrix of buildable apps for the CI for app in $(CUSTOM_NPM_APPS); do emit "$$app" "apps-custom/$$app" true false; done; \ for app in $(CUSTOM_COMPOSER_APPS); do emit "$$app" "apps-custom/$$app" false true; done; \ for app in $(EXTERNAL_FULL_APPS); do emit "$$app" "apps-external/$$app" true true; done; \ - for app in $(THEME_APPS); do emit "$$app" "themes/$$app" true false; done \ + for meta in $(SPECIAL_BUILD_APPS_META); do \ + app=$$(echo "$$meta" | cut -d"|" -f1); \ + path=$$(echo "$$meta" | cut -d"|" -f2); \ + has_npm=$$(echo "$$meta" | cut -d"|" -f3); \ + has_composer=$$(echo "$$meta" | cut -d"|" -f4); \ + emit "$$app" "$$path" "$$has_npm" "$$has_composer"; \ + done; \ ' | jq -s 'sort_by(.name)' From 8d5881da381767a8e97de8ffe3118b6b72d0a8a6 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:46:41 +0200 Subject: [PATCH 3/8] feat(Makefile): add .precheck target and gate IO-side targets on it Introduces the .precheck sentinel target that verifies preconditions (IONOS-side inputs available, .buildnumber tracked in clean, build completion summary printed) before any IO-side target runs, so a missing .buildnumber or unfetched submodule fails fast with a clear message instead of producing a half-built package. --- Makefile | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 1e10f17..f82ddef 100644 --- a/Makefile +++ b/Makefile @@ -35,7 +35,7 @@ EXTERNAL_FULL_TARGETS = $(patsubst %,build_%_app,$(EXTERNAL_FULL_APPS)) SPECIAL_BUILD_TARGETS = $(patsubst %,build_%_app,$(SPECIAL_BUILD_APPS)) # Core build targets -.PHONY: help clean +.PHONY: help clean .precheck # Main Nextcloud build .PHONY: build_nextcloud build_nextcloud_only build_nextcloud_dev dev_nextcloud # Applications — dynamically derived from category lists @@ -63,11 +63,52 @@ help: ## This help. @for app in $(EXTERNAL_FULL_APPS); do printf " \033[36m%-35s\033[0m %s\n" "build_$${app}_app" "apps-external composer+npm"; done @for app in $(SPECIAL_BUILD_APPS); do printf " \033[36m%-35s\033[0m %s\n" "build_$${app}_app" "special"; done +.precheck: + @{ \ + if [ ! -d "apps-external" ] || [ ! -d "apps-custom" ]; then \ + echo ""; \ + echo "**********************************************************************"; \ + echo "ERROR: apps-external/ or apps-custom/ not found!"; \ + echo ""; \ + echo "Run this Makefile from the Nextcloud project root:"; \ + echo " make -f IONOS/Makefile "; \ + echo "**********************************************************************"; \ + echo ""; \ + exit 1; \ + fi; \ + if ! test -f "version.php" || ! test -d "lib" || ! test -d "core"; then \ + echo ""; \ + echo "**********************************************************************"; \ + echo "ERROR: Not a valid Nextcloud project directory."; \ + echo ""; \ + echo "Run this Makefile from the Nextcloud project root:"; \ + echo " make -f IONOS/Makefile "; \ + echo "**********************************************************************"; \ + echo ""; \ + exit 1; \ + fi; \ + if ! command -v jq >/dev/null 2>&1; then \ + echo ""; \ + echo "**********************************************************************"; \ + echo "ERROR: jq is not installed!"; \ + echo ""; \ + echo "Please install jq:"; \ + echo " Ubuntu/Debian: sudo apt-get install jq"; \ + echo " macOS: brew install jq"; \ + echo " Other: https://jqlang.github.io/jq/download/"; \ + echo "**********************************************************************"; \ + echo ""; \ + exit 1; \ + fi; \ + } >&2 + clean: ## Clean up build artifacts @echo "[i] Cleaning build artifacts..." rm -rf node_modules rm -f version.json + rm -f .buildnumber rm -f $(TARGET_PACKAGE_NAME) + @echo "[✓] Clean completed" build_nextcloud_only: ## Build HiDrive Next only (no custom npm packages rebuild) set -e && \ @@ -133,16 +174,16 @@ build_nc-ionos-theme_app: ## Install and build ionos theme $(NPM_BUILD) @echo "[✓] nc-ionos-theme app built successfully" -add_config_partials: ## Copy custom config files to Nextcloud config +add_config_partials: .precheck ## Copy custom config files to Nextcloud config @echo "[i] Copying config files..." cp IONOS/configs/*.config.php config/ @echo "[✓] Config files copied successfully" -patch_shipped_json: ## Patch shipped.json to make core apps disableable +patch_shipped_json: .precheck ## Patch shipped.json to make core apps disableable @echo "[i] Patching shipped.json..." IONOS/apps-disable.sh -version.json: ## Generate version file +version.json: .precheck ## Generate version file @echo "[i] Generating version.json..." buildDate=$$(date +%s) && \ buildRef=$$(git rev-parse --short HEAD) && \ @@ -229,7 +270,7 @@ build_release: build_nextcloud .build_deps add_config_partials zip_dependencies build_locally: dev_nextcloud .build_deps ## Build all apps/themes for local development @echo "[i] Everything done for local/dev" -generate_apps_matrix_json: ## Generate JSON matrix of buildable apps for the CI pipeline +generate_apps_matrix_json: .precheck ## Generate JSON matrix of buildable apps for the CI pipeline @bash -c ' \ emit() { \ local app="$$1" path="$$2" has_npm="$$3" has_composer="$$4"; \ From 3d9d04ea50c2880b7d15928ede691f180c463a22 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:48:00 +0200 Subject: [PATCH 4/8] feat(Makefile): two-phase patch_shipped_json with APP_FOLDERS_TO_SHIP Adds scripts/patch_shipped_json_add_shipped_apps.sh and wires it into a two-phase patch_shipped_json target: phase 1 patches the bundled shipped.json with apps under APP_FOLDERS_TO_SHIP, phase 2 layers in any IONOS-specific overrides. Splitting the work keeps both phases scriptable and lets CI verify the result between phases. --- Makefile | 13 +- .../patch_shipped_json_add_shipped_apps.sh | 186 ++++++++++++++++++ 2 files changed, 198 insertions(+), 1 deletion(-) create mode 100755 scripts/patch_shipped_json_add_shipped_apps.sh diff --git a/Makefile b/Makefile index f82ddef..d26c0de 100644 --- a/Makefile +++ b/Makefile @@ -28,6 +28,12 @@ SPECIAL_BUILD_APPS_META = \ "nc_theming|apps-custom/nc_theming|false|true" \ "nc-ionos-theme|themes/nc-ionos-theme|true|false" +# App folders to add to shipped.json (makes apps non-removable) +# Add additional app folders here to include them in the shipped apps list +APP_FOLDERS_TO_SHIP = \ + apps-external \ + apps-custom + # Generate build target lists dynamically from category lists CUSTOM_NPM_TARGETS = $(patsubst %,build_%_app,$(CUSTOM_NPM_APPS)) CUSTOM_COMPOSER_TARGETS = $(patsubst %,build_%_app,$(CUSTOM_COMPOSER_APPS)) @@ -179,8 +185,13 @@ add_config_partials: .precheck ## Copy custom config files to Nextcloud config cp IONOS/configs/*.config.php config/ @echo "[✓] Config files copied successfully" -patch_shipped_json: .precheck ## Patch shipped.json to make core apps disableable +patch_shipped_json: .precheck ## Patch shipped.json @echo "[i] Patching shipped.json..." + + @echo "[i] Making external apps non-removable (hiding remove buttons)..." + IONOS/scripts/patch_shipped_json_add_shipped_apps.sh $(APP_FOLDERS_TO_SHIP) + + @echo "[i] Making core apps disableable and enforcing always-enabled apps..." IONOS/apps-disable.sh version.json: .precheck ## Generate version file diff --git a/scripts/patch_shipped_json_add_shipped_apps.sh b/scripts/patch_shipped_json_add_shipped_apps.sh new file mode 100755 index 0000000..3f9b161 --- /dev/null +++ b/scripts/patch_shipped_json_add_shipped_apps.sh @@ -0,0 +1,186 @@ +#!/bin/bash +set -euo pipefail + +# SPDX-FileCopyrightText: 2025 STRATO GmbH +# SPDX-License-Identifier: AGPL-3.0-or-later + +################################################################################ +# Patch shipped.json - Add Apps Script +################################################################################ +# +# DESCRIPTION: +# This script patches the core/shipped.json file by adding apps from +# specified app directories to the shippedApps array. It ensures that: +# 1. Apps are added to the end of the array for better diff visibility +# 2. Duplicate entries are prevented (uniqueness is maintained) +# 3. JSON structure remains valid +# +# LOCATION: +# This script is located in /IONOS/scripts/ as part of the IONOS submodule +# within the Nextcloud server repository. +# +# EXECUTION CONTEXT: +# ⚠️ IMPORTANT: This script should be executed during the Docker image build +# process, NOT at runtime in Kubernetes pods. +# +# USAGE: +# ./scripts/patch_shipped_json_add_shipped_apps.sh [app_folder2] ... +# +# Example: +# ./scripts/patch_shipped_json_add_shipped_apps.sh apps-external apps-custom +# +# ARGUMENTS: +# $@ - One or more app folder names relative to the Nextcloud root directory +# +# PREREQUISITES: +# - jq (JSON processor) must be installed +# - bash must be available +# - ../core/shipped.json must exist and be valid JSON +# - Specified app folders must exist +# +# OUTPUT: +# - Modifies ../core/shipped.json in place +# - Logs progress and results to stdout/stderr +# +# EXIT CODES: +# 0 - Success +# 1 - Fatal error (missing dependencies, invalid files, or processing errors) +# +# AUTHOR: IONOS Nextcloud Customization Team +# LICENSE: See LICENSES/ directory +# +################################################################################ + +# Configuration: Base directory and file paths +BDIR="$(dirname "${0}")" +SHIPPED_JSON="${BDIR}/../../core/shipped.json" + +################################################################################ +# Logging Functions +################################################################################ + +log_info() { + printf "\033[0;32m[✓]\033[0m %s\n" "${*}" +} + +log_warn() { + printf "\033[0;33m[!]\033[0m %s\n" "${*}" >&2 +} + +log_fatal() { + printf "\033[1;31m[✗]\033[0m Fatal Error: %s\n" "${*}" >&2 + exit 1 +} + +################################################################################ +# Utility Functions +################################################################################ + +command_exists() { + command -v "${1}" >/dev/null 2>&1 +} + +################################################################################ +# Core Functions +################################################################################ + +# Validate that shipped.json is valid JSON +# Performs a validation check on shipped.json using jq +# Usage: validate_shipped_json +# Exit: Calls log_fatal if JSON is invalid +validate_shipped_json() { + if ! jq empty "${SHIPPED_JSON}" 2>/dev/null; then + log_fatal "Invalid JSON in ${SHIPPED_JSON}" + fi +} + +# Add apps from a directory to shippedApps array +# Adds all apps from the specified directory to the shippedApps array +# while maintaining uniqueness and adding to the end of the array +# Usage: ship_apps_from_directory +# Arguments: +# $1 - Path to the app folder (relative or absolute) +# Side Effects: +# - Modifies shipped.json in place +# - Logs success messages for each added app +ship_apps_from_directory() { + local app_folder="${1}" + local app_folder_path="${BDIR}/../../${app_folder}" + local temp_file="${SHIPPED_JSON}.tmp" + local added_count=0 + local skipped_count=0 + + # Check if directory exists + if [ ! -d "${app_folder_path}" ]; then + log_warn "App folder does not exist: ${app_folder_path}" + return + fi + + log_info "Processing apps from '${app_folder}'..." + + # Iterate over each subdirectory in the app folder + for app_path in "${app_folder_path}"/*; do + # Skip if not a directory + if [ ! -d "${app_path}" ]; then + continue + fi + + # Extract app name from path + app_name=$(basename "${app_path}") + + # Check if app already exists in shippedApps + if jq -e --arg app "${app_name}" '.shippedApps | index($app)' "${SHIPPED_JSON}" >/dev/null 2>&1; then + log_info "App '${app_name}' already in shippedApps, skipping" + skipped_count=$((skipped_count + 1)) + continue + fi + + # Add app to the end of shippedApps array + if ! jq --arg app "${app_name}" \ + '.shippedApps += [$app]' \ + "${SHIPPED_JSON}" > "${temp_file}"; then + log_fatal "Failed to add app '${app_name}' with jq" + fi + + # Atomically replace the original file + mv "${temp_file}" "${SHIPPED_JSON}" + log_info "Added app '${app_name}' to shippedApps" + added_count=$((added_count + 1)) + done + + log_info "Processed ${app_folder}: ${added_count} apps added, ${skipped_count} apps skipped (already present)" +} + +main() { + # Check prerequisites + if ! command_exists jq; then + log_fatal "jq is required but not installed" + fi + + if [ ! -f "${SHIPPED_JSON}" ]; then + log_fatal "Shipped JSON file not found: ${SHIPPED_JSON}" + fi + + # Check if at least one app folder argument was provided + if [ $# -eq 0 ]; then + log_fatal "Usage: $(basename "$0") [app_folder2] ..." + fi + + # Validate shipped.json before processing + validate_shipped_json + + log_info "Starting to add apps to shippedApps array..." + + # Process each app folder provided as argument + for app_folder in "$@"; do + ship_apps_from_directory "${app_folder}" + done + + # Validate shipped.json after processing to ensure we didn't corrupt it + validate_shipped_json + + log_info "Successfully completed shipping apps from specified folders" +} + +# Execute main function with all arguments +main "$@" From 59c3e57329476eee8ed147ddd21fac3b235b7277 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:49:12 +0200 Subject: [PATCH 5/8] feat(Makefile): exclude apps via REMOVE_UNWANTED_APPS from removed-apps.txt Add a runtime-evaluated REMOVE_UNWANTED_APPS variable that reads IONOS/removed-apps.txt (skipping comments and blanks) and prefixes each entry with apps/. The result is fed to zip_dependencies via $(foreach app,$(REMOVE_UNWANTED_APPS),-x "$(app)/*"), so removed apps disappear from the final package without editing the Makefile. removed-apps.txt is created empty (header comment only) so the foreach expands to nothing today. --- Makefile | 6 +++++- removed-apps.txt | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 removed-apps.txt diff --git a/Makefile b/Makefile index d26c0de..0ec68c4 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,9 @@ APP_FOLDERS_TO_SHIP = \ apps-external \ apps-custom +# Apps to be removed from final package (read from removed-apps.txt) +REMOVE_UNWANTED_APPS = $(shell [ -f IONOS/removed-apps.txt ] && sed '/^#/d;/^$$/d;s/^/apps\//' IONOS/removed-apps.txt || echo "") + # Generate build target lists dynamically from category lists CUSTOM_NPM_TARGETS = $(patsubst %,build_%_app,$(CUSTOM_NPM_APPS)) CUSTOM_COMPOSER_TARGETS = $(patsubst %,build_%_app,$(CUSTOM_COMPOSER_APPS)) @@ -271,7 +274,8 @@ zip_dependencies: patch_shipped_json version.json ## Zip relevant files -x "package.json" \ -x "package-lock.json" \ -x "themes/nc-ionos-theme/README.md" \ - -x "themes/nc-ionos-theme/IONOS**" + -x "themes/nc-ionos-theme/IONOS**" \ + $(foreach app,$(REMOVE_UNWANTED_APPS),-x "$(app)/*") .build_deps: $(CUSTOM_NPM_TARGETS) $(CUSTOM_COMPOSER_TARGETS) $(EXTERNAL_FULL_TARGETS) $(SPECIAL_BUILD_TARGETS) diff --git a/removed-apps.txt b/removed-apps.txt new file mode 100644 index 0000000..5726d38 --- /dev/null +++ b/removed-apps.txt @@ -0,0 +1,13 @@ +# SPDX-FileCopyrightText: 2025 STRATO GmbH +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# Apps to exclude from the final hidrivenext-server.zip package. +# +# One app name per line. The Makefile prefixes each entry with "apps/" and +# passes them as -x exclusion patterns to the zip command. +# +# Lines starting with "#" and blank lines are ignored. +# +# Example: +# bruteforcesettings +# user_status From 67288e61b3523969886539a4c19007e1fbf4d71c Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:49:54 +0200 Subject: [PATCH 6/8] feat(Makefile): add pipeline targets build_after_external_apps + package_after_build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two new targets for staged CI pipelines that build external apps in a separate job: build_after_external_apps : build_nextcloud + add_config_partials (runs after the external-apps matrix finishes — does NOT rebuild them) package_after_build : alias for zip_dependencies, named to make the pipeline stage intent obvious The existing build_release / build_locally aggregates are unchanged. --- Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Makefile b/Makefile index 0ec68c4..353459d 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,8 @@ SPECIAL_BUILD_TARGETS = $(patsubst %,build_%_app,$(SPECIAL_BUILD_APPS)) .PHONY: add_config_partials patch_shipped_json version.json zip_dependencies # Meta targets .PHONY: .build_deps build_release build_locally +# Pipeline targets for CI workflow +.PHONY: build_after_external_apps package_after_build # CI matrix generation .PHONY: generate_apps_matrix_json @@ -279,6 +281,12 @@ zip_dependencies: patch_shipped_json version.json ## Zip relevant files .build_deps: $(CUSTOM_NPM_TARGETS) $(CUSTOM_COMPOSER_TARGETS) $(EXTERNAL_FULL_TARGETS) $(SPECIAL_BUILD_TARGETS) +build_after_external_apps: build_nextcloud add_config_partials ## Build HiDrive Next and add configs after external apps are done + @echo "[i] HiDrive Next built and config files added" + +package_after_build: zip_dependencies ## Create package after build is complete + @echo "[i] Package created successfully" + build_release: build_nextcloud .build_deps add_config_partials zip_dependencies ## Build a release package (build apps/themes, copy configs and package) @echo "[i] Everything done for a release" From de86acd2e20d98bc14cef3f6430100484d7d784c Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:50:58 +0200 Subject: [PATCH 7/8] feat(scripts): add validate_app_list_uniqueness + validate_external_apps + validate_all umbrella MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two validation scripts and Makefile wiring: - scripts/validate_app_list_uniqueness.sh — fails the build if an app appears in more than one category list (e.g., both custom and external), since duplicates break the matrix generator. - scripts/validate_external_apps.sh — verifies every entry in EXTERNAL_FULL_TARGETS actually exists under apps-external/ with a buildable appinfo, catching submodule drift. - validate_all — umbrella target that runs both, plus a final '[i] Package … created successfully' line in zip_dependencies so the CI log clearly marks the end of packaging. --- Makefile | 24 +++ scripts/validate_app_list_uniqueness.sh | 98 ++++++++++++ scripts/validate_external_apps.sh | 193 ++++++++++++++++++++++++ 3 files changed, 315 insertions(+) create mode 100755 scripts/validate_app_list_uniqueness.sh create mode 100755 scripts/validate_external_apps.sh diff --git a/Makefile b/Makefile index 353459d..bf81c19 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,8 @@ SPECIAL_BUILD_TARGETS = $(patsubst %,build_%_app,$(SPECIAL_BUILD_APPS)) .PHONY: build_after_external_apps package_after_build # CI matrix generation .PHONY: generate_apps_matrix_json +# Validation targets +.PHONY: validate_app_list_uniqueness validate_external_apps validate_all # HELP # This will output the help for each task @@ -278,6 +280,7 @@ zip_dependencies: patch_shipped_json version.json ## Zip relevant files -x "themes/nc-ionos-theme/README.md" \ -x "themes/nc-ionos-theme/IONOS**" \ $(foreach app,$(REMOVE_UNWANTED_APPS),-x "$(app)/*") + @echo "[i] Package $(TARGET_PACKAGE_NAME) created successfully" .build_deps: $(CUSTOM_NPM_TARGETS) $(CUSTOM_COMPOSER_TARGETS) $(EXTERNAL_FULL_TARGETS) $(SPECIAL_BUILD_TARGETS) @@ -293,6 +296,27 @@ build_release: build_nextcloud .build_deps add_config_partials zip_dependencies build_locally: dev_nextcloud .build_deps ## Build all apps/themes for local development @echo "[i] Everything done for local/dev" +validate_app_list_uniqueness: .precheck ## Validate that apps are only in one list and not duplicated by hardcoded targets + @IONOS/scripts/validate_app_list_uniqueness.sh \ + "$(CUSTOM_NPM_APPS)" \ + "$(CUSTOM_COMPOSER_APPS)" \ + "$(EXTERNAL_FULL_APPS)" \ + "$(SPECIAL_BUILD_APPS)" \ + "$(MAKEFILE_LIST)" + +validate_external_apps: .precheck ## Validate and suggest proper categorization for apps-custom/ and apps-external/ + @IONOS/scripts/validate_external_apps.sh \ + "$(CUSTOM_NPM_APPS)" \ + "$(CUSTOM_COMPOSER_APPS)" \ + "$(EXTERNAL_FULL_APPS)" \ + "$(SPECIAL_BUILD_APPS)" + +validate_all: .precheck ## Run all validation tasks + @echo "[i] Running validation..." + @$(MAKE) -f IONOS/Makefile validate_app_list_uniqueness + @$(MAKE) -f IONOS/Makefile validate_external_apps + @echo "[✓] Validation completed successfully" + generate_apps_matrix_json: .precheck ## Generate JSON matrix of buildable apps for the CI pipeline @bash -c ' \ emit() { \ diff --git a/scripts/validate_app_list_uniqueness.sh b/scripts/validate_app_list_uniqueness.sh new file mode 100755 index 0000000..fdea91e --- /dev/null +++ b/scripts/validate_app_list_uniqueness.sh @@ -0,0 +1,98 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2025 STRATO GmbH +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -euo pipefail + +# Validates that apps are only in one list and not duplicated by hardcoded targets. +# Arguments: +# $1 CUSTOM_NPM_APPS — space-separated list +# $2 CUSTOM_COMPOSER_APPS — space-separated list +# $3 EXTERNAL_FULL_APPS — space-separated list +# $4 SPECIAL_BUILD_APPS — space-separated list +# $5 MAKEFILE_PATH — path to the Makefile to inspect for hardcoded targets + +CUSTOM_NPM_APPS="$1" +CUSTOM_COMPOSER_APPS="$2" +EXTERNAL_FULL_APPS="$3" +SPECIAL_BUILD_APPS="$4" +MAKEFILE_PATH="$5" + +echo "[i] Validating app list uniqueness..." + +validation_failed=0 + +all_apps="$CUSTOM_NPM_APPS $CUSTOM_COMPOSER_APPS $EXTERNAL_FULL_APPS $SPECIAL_BUILD_APPS" + +# ── 1. Cross-list duplicate check ───────────────────────────────────────────── + +echo "[i] Checking for duplicate apps across lists..." +echo "" + +for app in $all_apps; do + count=0 + locations="" + + for a in $CUSTOM_NPM_APPS; do [ "$app" = "$a" ] && { count=$((count + 1)); locations="$locations CUSTOM_NPM_APPS"; break; }; done + for a in $CUSTOM_COMPOSER_APPS; do [ "$app" = "$a" ] && { count=$((count + 1)); locations="$locations CUSTOM_COMPOSER_APPS"; break; }; done + for a in $EXTERNAL_FULL_APPS; do [ "$app" = "$a" ] && { count=$((count + 1)); locations="$locations EXTERNAL_FULL_APPS"; break; }; done + for a in $SPECIAL_BUILD_APPS; do [ "$app" = "$a" ] && { count=$((count + 1)); locations="$locations SPECIAL_BUILD_APPS"; break; }; done + + if [ "$count" -gt 1 ]; then + echo "ERROR: App \"$app\" appears in multiple lists:$locations" + validation_failed=1 + fi +done + +# ── 2. Hardcoded-target conflict check ──────────────────────────────────────── +# Apps in the three dynamic categories must NOT have a hardcoded build__app +# target — such a target would shadow the dynamic pattern rule. + +echo "[i] Checking for hardcoded build targets that conflict with dynamic lists..." +echo "" + +dynamic_apps="$CUSTOM_NPM_APPS $CUSTOM_COMPOSER_APPS $EXTERNAL_FULL_APPS" + +# Find all hardcoded build_*_app: targets in the Makefile (one or more, in case +# MAKEFILE_LIST contains multiple files separated by spaces). +hardcoded_targets=$(grep -hE "^build_[a-zA-Z0-9_-]+_app:" $MAKEFILE_PATH 2>/dev/null | sed 's/^build_//;s/_app:.*//' || true) + +for target in $hardcoded_targets; do + for app in $dynamic_apps; do + if [ "$target" = "$app" ]; then + echo "ERROR: App \"$app\" has a hardcoded build_${app}_app target but is also in a dynamic list" + echo " Either remove the hardcoded target and rely on dynamic rules, or move the app to SPECIAL_BUILD_APPS" + validation_failed=1 + break + fi + done +done + +# ── 3. SPECIAL_BUILD_APPS must have hardcoded targets ───────────────────────── + +echo "[i] Checking that SPECIAL_BUILD_APPS have corresponding hardcoded targets..." +echo "" + +for app in $SPECIAL_BUILD_APPS; do + found=0 + for target in $hardcoded_targets; do + if [ "$app" = "$target" ]; then + found=1 + break + fi + done + + if [ "$found" -eq 0 ]; then + echo "ERROR: App \"$app\" is in SPECIAL_BUILD_APPS but has no hardcoded build_${app}_app target" + echo " Either add a hardcoded target or move the app to an appropriate dynamic list" + validation_failed=1 + fi +done + +if [ "$validation_failed" -eq 0 ]; then + echo "[✓] All apps are uniquely categorized with no conflicts" +else + echo "" + echo "[✗] Validation failed — please fix the issues above" + exit 1 +fi diff --git a/scripts/validate_external_apps.sh b/scripts/validate_external_apps.sh new file mode 100755 index 0000000..198d6f8 --- /dev/null +++ b/scripts/validate_external_apps.sh @@ -0,0 +1,193 @@ +#!/bin/bash +# SPDX-FileCopyrightText: 2025 STRATO GmbH +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -euo pipefail + +# Validates the apps under apps-custom/ and apps-external/ and suggests the +# correct HiDrive Next Makefile category for each one. +# +# Arguments: +# $1 CUSTOM_NPM_APPS — apps-custom, npm only +# $2 CUSTOM_COMPOSER_APPS — apps-custom, composer only +# $3 EXTERNAL_FULL_APPS — apps-external, composer + npm +# $4 SPECIAL_BUILD_APPS — apps with dedicated build targets + +CUSTOM_NPM_APPS="$1" +CUSTOM_COMPOSER_APPS="$2" +EXTERNAL_FULL_APPS="$3" +SPECIAL_BUILD_APPS="$4" + +echo "[i] Analyzing apps under apps-custom/ and apps-external/..." + +validation_failed=0 +missing_app_list="" +unconfigured_app_list="" +review_app_list="" + +# ── 1. Submodule status pass ────────────────────────────────────────────────── + +echo "" +echo "[i] Checking git submodule status..." + +if command -v git >/dev/null 2>&1; then + submodule_status_output=$(git submodule status 2>/dev/null || echo "") + if [ -n "$submodule_status_output" ]; then + while IFS= read -r line; do + [ -z "$line" ] && continue + status_char=${line:0:1} + submodule_path=$(echo "$line" | awk '{print $2}') + app_name=$(basename "$submodule_path") + + case "$status_char" in + " ") echo " [✓] $app_name: submodule up to date" ;; + "+") echo " [!] $app_name: submodule has uncommitted changes" ;; + "-") + echo " [✗] $app_name: submodule not initialized" + echo " Run: git submodule update --init $submodule_path" + validation_failed=1 + ;; + "U") + echo " [✗] $app_name: submodule has merge conflicts" + validation_failed=1 + ;; + *) echo " [?] $app_name: unknown submodule status ($status_char)" ;; + esac + done <<< "$submodule_status_output" + else + echo " [i] No git submodules found (or git submodule command failed)" + fi +else + echo " [!] git not available — skipping submodule status check" +fi + +# ── 2. Configured apps must exist on disk ───────────────────────────────────── + +echo "" +echo "[i] Checking configured apps for missing directories..." + +check_path_for() { + # echo the expected path for a given app+category combination + local app="$1" category="$2" + case "$category" in + CUSTOM_NPM_APPS|CUSTOM_COMPOSER_APPS) echo "apps-custom/$app" ;; + EXTERNAL_FULL_APPS) echo "apps-external/$app" ;; + SPECIAL_BUILD_APPS) + # specials may live in apps-custom/ OR themes/ + if [ -d "apps-custom/$app" ]; then echo "apps-custom/$app" + elif [ -d "themes/$app" ]; then echo "themes/$app" + else echo "apps-custom/$app"; fi + ;; + esac +} + +for category in CUSTOM_NPM_APPS CUSTOM_COMPOSER_APPS EXTERNAL_FULL_APPS SPECIAL_BUILD_APPS; do + eval "list=\"\$$category\"" + for app in $list; do + path=$(check_path_for "$app" "$category") + if [ ! -d "$path" ]; then + echo " [✗] $app ($category): expected directory $path is missing" + missing_app_list="$missing_app_list $app" + validation_failed=1 + else + echo " [✓] $app ($category) → $path" + fi + done +done + +# ── 3. Walk apps-custom/ and apps-external/, suggest a category ─────────────── + +is_in_list() { + local needle="$1" haystack="$2" + for x in $haystack; do + [ "$x" = "$needle" ] && return 0 + done + return 1 +} + +analyze_app() { + local app="$1" path="$2" expected_dir="$3" + + # Skip apps that have a hardcoded special target in the Makefile + if is_in_list "$app" "$SPECIAL_BUILD_APPS"; then + echo " [✓] $app → SPECIAL_BUILD_APPS (already configured)" + return + fi + + local has_composer=0 has_package=0 has_build=0 current="" + [ -f "$path/composer.json" ] && has_composer=1 + [ -f "$path/package.json" ] && has_package=1 + if [ "$has_package" -eq 1 ] && grep -q '"build"' "$path/package.json" 2>/dev/null; then + has_build=1 + fi + + if is_in_list "$app" "$CUSTOM_NPM_APPS"; then current="CUSTOM_NPM_APPS" + elif is_in_list "$app" "$CUSTOM_COMPOSER_APPS"; then current="CUSTOM_COMPOSER_APPS" + elif is_in_list "$app" "$EXTERNAL_FULL_APPS"; then current="EXTERNAL_FULL_APPS" + fi + + # Decide the recommended category based on layout + local recommended="" + if [ "$expected_dir" = "apps-external" ]; then + if [ "$has_composer" -eq 1 ] && [ "$has_package" -eq 1 ] && [ "$has_build" -eq 1 ]; then + recommended="EXTERNAL_FULL_APPS" + elif [ "$has_composer" -eq 1 ]; then + recommended="SPECIAL_BUILD_APPS" # apps-external + composer-only doesn't have a dynamic category yet + else + recommended="SPECIAL_BUILD_APPS" + fi + else + # apps-custom/ + # Priority: if package.json has a build script, treat as npm-driven even if composer.json is present. + # composer.json may exist for dev tooling without being part of the release build. + if [ "$has_build" -eq 1 ]; then + recommended="CUSTOM_NPM_APPS" + elif [ "$has_composer" -eq 1 ] && [ "$has_package" -eq 0 ]; then + recommended="CUSTOM_COMPOSER_APPS" + else + recommended="SPECIAL_BUILD_APPS" + fi + fi + + if [ -z "$current" ]; then + echo " [!] $app: NOT IN ANY LIST — suggest $recommended (add to list or removed-apps.txt)" + unconfigured_app_list="$unconfigured_app_list $app" + elif [ "$current" != "$recommended" ]; then + echo " [!] $app: in $current — Makefile analysis suggests $recommended" + review_app_list="$review_app_list $app" + else + echo " [✓] $app: $current matches Makefile analysis" + fi +} + +echo "" +echo "[i] Analyzing apps under apps-custom/..." +if [ -d "apps-custom" ]; then + for app_path in apps-custom/*/; do + [ -d "$app_path" ] || continue + app=$(basename "$app_path") + analyze_app "$app" "${app_path%/}" "apps-custom" + done +fi + +echo "" +echo "[i] Analyzing apps under apps-external/..." +if [ -d "apps-external" ]; then + for app_path in apps-external/*/; do + [ -d "$app_path" ] || continue + app=$(basename "$app_path") + analyze_app "$app" "${app_path%/}" "apps-external" + done +fi + +# ── Summary ─────────────────────────────────────────────────────────────────── + +echo "" +if [ "$validation_failed" -eq 0 ] && [ -z "$review_app_list" ] && [ -z "$unconfigured_app_list" ]; then + echo "[✓] All apps are properly configured" +else + if [ -n "$missing_app_list" ]; then echo "[✗] Missing directories:$missing_app_list"; fi + if [ -n "$unconfigured_app_list" ]; then echo "[!] Apps on disk not in any list (advisory):$unconfigured_app_list"; fi + if [ -n "$review_app_list" ]; then echo "[!] Apps possibly miscategorized:$review_app_list"; fi + if [ "$validation_failed" -ne 0 ]; then exit 1; fi +fi From 797b3c703326486bca3d285e33ec60d60b1b6c44 Mon Sep 17 00:00:00 2001 From: "Misha M.-Kupriyanov" Date: Mon, 11 May 2026 17:54:37 +0200 Subject: [PATCH 8/8] chore(Makefile): update copyright header to 2025 STRATO GmbH --- Makefile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Makefile b/Makefile index bf81c19..9f0bd8c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,4 @@ -# SPDX-FileCopyrightText: 2024 Kai Henseler # SPDX-FileCopyrightText: 2025 STRATO GmbH -# # SPDX-License-Identifier: AGPL-3.0-or-later # Build configuration