All checks were successful
Test Module Installation Action / Node.js Module with NPM (pull_request) Successful in 19s
Test Module Installation Action / PHP Module with Composer (pull_request) Successful in 54s
Test Module Installation Action / Mixed PHP and Node Modules (pull_request) Successful in 1m12s
Test Module Installation Action / Single Module (No Dependencies) (pull_request) Successful in 1m35s
Test Module Installation Action / Test Summary (pull_request) Successful in 3s
Signed-off-by: Sebastian Krupinski <root@LAPTOP-7DVOR6NC>
415 lines
14 KiB
YAML
415 lines
14 KiB
YAML
name: 'Install Nodarx Modules'
|
|
description: 'Installs custom PHP/Vue modules from git repositories with dependency resolution'
|
|
author: 'Nodarx'
|
|
|
|
branding:
|
|
icon: 'package'
|
|
color: 'purple'
|
|
|
|
inputs:
|
|
modules:
|
|
description: 'JSON array of modules to install. Example: [{"name":"mail","repo":"https://git.ktrix.dev/Nodarx/mail","branch":"main","dependencies":["mail_manager"]}]'
|
|
required: true
|
|
|
|
install-path:
|
|
description: 'Base directory where modules will be installed'
|
|
required: false
|
|
default: './modules'
|
|
|
|
skip-dependencies-check:
|
|
description: 'Skip dependency validation (not recommended)'
|
|
required: false
|
|
default: 'false'
|
|
|
|
outputs:
|
|
installed-modules:
|
|
description: 'Comma-separated list of installed module names in installation order'
|
|
value: ${{ steps.install.outputs.installed_modules }}
|
|
|
|
install-path:
|
|
description: 'Path where modules were installed'
|
|
value: ${{ steps.install.outputs.install_path }}
|
|
|
|
status:
|
|
description: 'Installation status (success/failure)'
|
|
value: ${{ steps.install.outputs.status }}
|
|
|
|
runs:
|
|
using: 'composite'
|
|
steps:
|
|
- name: Check prerequisites
|
|
shell: bash
|
|
run: |
|
|
echo "::group::Checking prerequisites"
|
|
|
|
# Check for required commands
|
|
MISSING_CMDS=()
|
|
for cmd in git jq; do
|
|
if ! command -v $cmd &> /dev/null; then
|
|
MISSING_CMDS+=($cmd)
|
|
fi
|
|
done
|
|
|
|
# Install missing commands
|
|
if [ ${#MISSING_CMDS[@]} -gt 0 ]; then
|
|
echo "Installing missing commands: ${MISSING_CMDS[*]}"
|
|
sudo apt-get update -qq
|
|
for cmd in "${MISSING_CMDS[@]}"; do
|
|
sudo apt-get install -y $cmd
|
|
done
|
|
fi
|
|
|
|
# Verify all commands are now available
|
|
for cmd in git jq; do
|
|
if ! command -v $cmd &> /dev/null; then
|
|
echo "::error::Required command '$cmd' not found after installation attempt."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "✓ All prerequisites met"
|
|
echo "::endgroup::"
|
|
|
|
- name: Validate and parse modules JSON
|
|
id: parse
|
|
shell: bash
|
|
env:
|
|
MODULES_JSON: ${{ inputs.modules }}
|
|
SKIP_DEPS_CHECK: ${{ inputs.skip-dependencies-check }}
|
|
run: |
|
|
echo "::group::Validating modules configuration"
|
|
|
|
# Validate JSON syntax
|
|
if ! echo "$MODULES_JSON" | jq empty 2>/dev/null; then
|
|
echo "::error::Invalid JSON in modules input"
|
|
exit 1
|
|
fi
|
|
|
|
# Validate JSON is an array
|
|
if [ "$(echo "$MODULES_JSON" | jq 'type')" != '"array"' ]; then
|
|
echo "::error::Modules input must be a JSON array"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if array is empty
|
|
MODULE_COUNT=$(echo "$MODULES_JSON" | jq 'length')
|
|
if [ "$MODULE_COUNT" -eq 0 ]; then
|
|
echo "::error::Modules array is empty. Please specify at least one module."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found $MODULE_COUNT module(s) to install"
|
|
|
|
# Validate each module has required fields
|
|
INVALID_MODULES=$(echo "$MODULES_JSON" | jq -r '
|
|
to_entries[] |
|
|
select(.value.name == null or .value.name == "" or .value.repo == null or .value.repo == "") |
|
|
.key
|
|
')
|
|
|
|
if [ -n "$INVALID_MODULES" ]; then
|
|
echo "::error::Modules at indices $INVALID_MODULES are missing required fields 'name' or 'repo'"
|
|
exit 1
|
|
fi
|
|
|
|
# Extract all module names
|
|
ALL_MODULES=$(echo "$MODULES_JSON" | jq -r '.[].name' | tr '\n' ' ')
|
|
echo "Modules to install: $ALL_MODULES"
|
|
|
|
# Validate dependencies exist (if not skipped)
|
|
if [ "$SKIP_DEPS_CHECK" != "true" ]; then
|
|
MISSING_DEPS=$(echo "$MODULES_JSON" | jq -r --arg all_modules "$ALL_MODULES" '
|
|
.[] |
|
|
select(.dependencies != null) |
|
|
.dependencies[] as $dep |
|
|
select(($all_modules | contains($dep)) | not) |
|
|
$dep
|
|
' | sort -u)
|
|
|
|
if [ -n "$MISSING_DEPS" ]; then
|
|
echo "::error::The following dependencies are referenced but not defined: $MISSING_DEPS"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "✓ Modules configuration is valid"
|
|
echo "::endgroup::"
|
|
|
|
- name: Order modules by dependencies
|
|
id: order
|
|
shell: bash
|
|
env:
|
|
MODULES_JSON: ${{ inputs.modules }}
|
|
SKIP_DEPS_CHECK: ${{ inputs.skip-dependencies-check }}
|
|
run: |
|
|
echo "::group::Calculating installation order"
|
|
|
|
# Create temporary files for topological sort
|
|
TEMP_DIR=$(mktemp -d)
|
|
EDGES_FILE="$TEMP_DIR/edges"
|
|
NODES_FILE="$TEMP_DIR/nodes"
|
|
|
|
# Extract all module names
|
|
echo "$MODULES_JSON" | jq -r '.[].name' > "$NODES_FILE"
|
|
|
|
# Create dependency edges (dependent -> dependency)
|
|
# For each module with dependencies, create "module dep" pairs
|
|
echo "$MODULES_JSON" | jq -r '
|
|
.[] |
|
|
select(.dependencies != null and (.dependencies | length) > 0) |
|
|
. as $item |
|
|
.dependencies[] |
|
|
($item.name + " " + .)
|
|
' > "$EDGES_FILE"
|
|
|
|
# Perform topological sort
|
|
if [ "$SKIP_DEPS_CHECK" != "true" ] && [ -s "$EDGES_FILE" ]; then
|
|
# Check for circular dependencies
|
|
if ! tsort "$EDGES_FILE" &>/dev/null; then
|
|
echo "::error::Circular dependency detected. Cannot determine installation order."
|
|
cat "$EDGES_FILE"
|
|
rm -rf "$TEMP_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
# Get sorted order (reverse tsort output since it gives reverse topological order)
|
|
INSTALL_ORDER=$(cat "$NODES_FILE" "$EDGES_FILE" | tsort | tac | tr '\n' ' ')
|
|
else
|
|
# No dependencies or skip check - install in order given
|
|
INSTALL_ORDER=$(cat "$NODES_FILE" | tr '\n' ' ')
|
|
fi
|
|
|
|
# Clean up
|
|
rm -rf "$TEMP_DIR"
|
|
|
|
echo "Installation order: $INSTALL_ORDER"
|
|
echo "install_order=$INSTALL_ORDER" >> $GITHUB_OUTPUT
|
|
|
|
echo "✓ Installation order determined"
|
|
echo "::endgroup::"
|
|
|
|
- name: Clone module repositories
|
|
id: clone
|
|
shell: bash
|
|
env:
|
|
MODULES_JSON: ${{ inputs.modules }}
|
|
INSTALL_PATH: ${{ inputs.install-path }}
|
|
INSTALL_ORDER: ${{ steps.order.outputs.install_order }}
|
|
run: |
|
|
echo "::group::Cloning module repositories"
|
|
|
|
# Create base install directory
|
|
mkdir -p "$INSTALL_PATH"
|
|
|
|
# Clone each module in order
|
|
for MODULE_NAME in $INSTALL_ORDER; do
|
|
echo ""
|
|
echo "=== Cloning module: $MODULE_NAME ==="
|
|
|
|
# Extract module info from JSON
|
|
MODULE_INFO=$(echo "$MODULES_JSON" | jq -r --arg name "$MODULE_NAME" '
|
|
.[] | select(.name == $name)
|
|
')
|
|
|
|
REPO_URL=$(echo "$MODULE_INFO" | jq -r '.repo')
|
|
BRANCH=$(echo "$MODULE_INFO" | jq -r '.branch // empty')
|
|
MODULE_PATH="$INSTALL_PATH/$MODULE_NAME"
|
|
|
|
echo "Repository: $REPO_URL"
|
|
if [ -n "$BRANCH" ]; then
|
|
echo "Branch: $BRANCH"
|
|
else
|
|
echo "Branch: (repository default)"
|
|
fi
|
|
echo "Path: $MODULE_PATH"
|
|
|
|
# Clone the repository
|
|
if [ -d "$MODULE_PATH" ]; then
|
|
echo "::warning::Directory $MODULE_PATH already exists, pulling latest changes..."
|
|
cd "$MODULE_PATH"
|
|
if [ -n "$BRANCH" ]; then
|
|
git pull origin "$BRANCH" || echo "::warning::Pull failed, continuing with existing code"
|
|
else
|
|
git pull || echo "::warning::Pull failed, continuing with existing code"
|
|
fi
|
|
cd - > /dev/null
|
|
else
|
|
if [ -n "$BRANCH" ]; then
|
|
if git clone --branch "$BRANCH" --depth 1 "$REPO_URL" "$MODULE_PATH"; then
|
|
echo "✓ Cloned $MODULE_NAME successfully"
|
|
else
|
|
echo "::error::Failed to clone $MODULE_NAME from $REPO_URL (branch: $BRANCH)"
|
|
exit 1
|
|
fi
|
|
else
|
|
if git clone --depth 1 "$REPO_URL" "$MODULE_PATH"; then
|
|
echo "✓ Cloned $MODULE_NAME successfully"
|
|
else
|
|
echo "::error::Failed to clone $MODULE_NAME from $REPO_URL"
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
echo "✓ All modules cloned successfully"
|
|
echo "::endgroup::"
|
|
|
|
- name: Install module dependencies
|
|
id: install
|
|
shell: bash
|
|
env:
|
|
INSTALL_PATH: ${{ inputs.install-path }}
|
|
INSTALL_ORDER: ${{ steps.order.outputs.install_order }}
|
|
run: |
|
|
echo "::group::Installing module dependencies"
|
|
|
|
INSTALLED_MODULES=()
|
|
|
|
for MODULE_NAME in $INSTALL_ORDER; do
|
|
MODULE_PATH="$INSTALL_PATH/$MODULE_NAME"
|
|
|
|
echo ""
|
|
echo "=== Installing dependencies for: $MODULE_NAME ==="
|
|
echo "Path: $MODULE_PATH"
|
|
|
|
if [ ! -d "$MODULE_PATH" ]; then
|
|
echo "::error::Module directory not found: $MODULE_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
cd "$MODULE_PATH"
|
|
|
|
# Detect and install PHP dependencies
|
|
if [ -f "composer.json" ]; then
|
|
echo "Found composer.json - installing PHP dependencies..."
|
|
|
|
# Check if composer is available
|
|
if ! command -v composer &> /dev/null; then
|
|
echo "::error::composer not found. Please ensure PHP and Composer are installed (use action-server-install with install-php: true)"
|
|
exit 1
|
|
fi
|
|
|
|
# Install with composer
|
|
if composer install --no-dev --optimize-autoloader --no-interaction; then
|
|
echo "✓ Composer dependencies installed"
|
|
else
|
|
echo "::error::Failed to install composer dependencies for $MODULE_NAME"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo "No composer.json found, skipping PHP dependencies"
|
|
fi
|
|
|
|
# Detect and install Node dependencies
|
|
if [ -f "package.json" ]; then
|
|
echo "Found package.json - installing Node dependencies..."
|
|
|
|
# Check if npm is available
|
|
if ! command -v npm &> /dev/null; then
|
|
echo "::error::npm not found. Please ensure Node.js is installed (use action-server-install with install-node: true)"
|
|
exit 1
|
|
fi
|
|
|
|
# Use npm ci if lockfile exists, otherwise npm install
|
|
if [ -f "package-lock.json" ]; then
|
|
echo "Using npm ci (lockfile found)..."
|
|
if npm ci --production; then
|
|
echo "✓ npm dependencies installed (with lockfile)"
|
|
else
|
|
echo "::warning::npm ci failed, trying npm install..."
|
|
if npm install --production; then
|
|
echo "✓ npm dependencies installed"
|
|
else
|
|
echo "::error::Failed to install npm dependencies for $MODULE_NAME"
|
|
exit 1
|
|
fi
|
|
fi
|
|
else
|
|
echo "Using npm install (no lockfile)..."
|
|
if npm install --production; then
|
|
echo "✓ npm dependencies installed"
|
|
else
|
|
echo "::error::Failed to install npm dependencies for $MODULE_NAME"
|
|
exit 1
|
|
fi
|
|
fi
|
|
else
|
|
echo "No package.json found, skipping Node dependencies"
|
|
fi
|
|
|
|
# Return to previous directory
|
|
cd - > /dev/null
|
|
|
|
INSTALLED_MODULES+=("$MODULE_NAME")
|
|
echo "✓ $MODULE_NAME installation complete"
|
|
done
|
|
|
|
# Set outputs
|
|
INSTALLED_LIST=$(IFS=,; echo "${INSTALLED_MODULES[*]}")
|
|
echo "installed_modules=$INSTALLED_LIST" >> $GITHUB_OUTPUT
|
|
echo "install_path=$INSTALL_PATH" >> $GITHUB_OUTPUT
|
|
echo "status=success" >> $GITHUB_OUTPUT
|
|
|
|
echo ""
|
|
echo "::notice::✓ Successfully installed ${#INSTALLED_MODULES[@]} module(s): $INSTALLED_LIST"
|
|
echo "::endgroup::"
|
|
|
|
- name: Verify installations
|
|
shell: bash
|
|
env:
|
|
INSTALL_PATH: ${{ inputs.install-path }}
|
|
INSTALL_ORDER: ${{ steps.order.outputs.install_order }}
|
|
run: |
|
|
echo "::group::Verifying module installations"
|
|
|
|
for MODULE_NAME in $INSTALL_ORDER; do
|
|
MODULE_PATH="$INSTALL_PATH/$MODULE_NAME"
|
|
|
|
echo ""
|
|
echo "Verifying: $MODULE_NAME"
|
|
|
|
# Check module directory exists
|
|
if [ ! -d "$MODULE_PATH" ]; then
|
|
echo "::error::Module directory not found: $MODULE_PATH"
|
|
exit 1
|
|
fi
|
|
|
|
# Check .git directory exists
|
|
if [ ! -d "$MODULE_PATH/.git" ]; then
|
|
echo "::warning::No .git directory found in $MODULE_NAME"
|
|
fi
|
|
|
|
# Verify composer installation
|
|
if [ -f "$MODULE_PATH/composer.json" ]; then
|
|
if [ -d "$MODULE_PATH/vendor" ]; then
|
|
echo " ✓ PHP dependencies installed (vendor/ exists)"
|
|
else
|
|
echo "::error::composer.json exists but vendor/ directory not found in $MODULE_NAME"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Verify npm installation
|
|
if [ -f "$MODULE_PATH/package.json" ]; then
|
|
NODE_DEP_COUNT=$(jq -r '((.dependencies // {}) | length) + ((.optionalDependencies // {}) | length)' "$MODULE_PATH/package.json" 2>/dev/null || echo "0")
|
|
if [ "$NODE_DEP_COUNT" -gt 0 ]; then
|
|
if [ -d "$MODULE_PATH/node_modules" ]; then
|
|
echo " ✓ Node dependencies installed (node_modules/ exists)"
|
|
else
|
|
echo "::error::package.json has production dependencies but node_modules/ directory not found in $MODULE_NAME"
|
|
exit 1
|
|
fi
|
|
else
|
|
echo " ✓ Node module has no production dependencies (node_modules/ not required)"
|
|
fi
|
|
fi
|
|
|
|
echo " ✓ $MODULE_NAME verified"
|
|
done
|
|
|
|
echo ""
|
|
echo "✓ All modules verified successfully"
|
|
echo "::endgroup::"
|