MAESTRO: add OpenClaw gateway detection, plugin install, and memory slot config to install.sh
Add find_openclaw()/check_openclaw() for gateway detection across PATH, ~/.openclaw/, /usr/local/bin/, and node_modules paths. Add install_plugin() that clones, builds, creates installable package, and runs plugins install/enable following the Dockerfile.e2e flow. Add configure_memory_slot() that creates or updates ~/.openclaw/openclaw.json with plugins.slots.memory="claude-mem" while preserving existing config. Includes test-install.sh with 23 passing tests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+224
-2
@@ -267,6 +267,211 @@ install_uv() {
|
||||
success "uv ${uv_version} installed at ${UV_PATH}"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# OpenClaw gateway detection
|
||||
###############################################################################
|
||||
|
||||
OPENCLAW_PATH=""
|
||||
|
||||
find_openclaw() {
|
||||
# Try PATH first
|
||||
if command -v openclaw.mjs &>/dev/null; then
|
||||
OPENCLAW_PATH="$(command -v openclaw.mjs)"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check common installation paths
|
||||
local -a openclaw_paths=(
|
||||
"${HOME}/.openclaw/openclaw.mjs"
|
||||
"/usr/local/bin/openclaw.mjs"
|
||||
"/usr/local/lib/node_modules/openclaw/openclaw.mjs"
|
||||
"${HOME}/.npm-global/lib/node_modules/openclaw/openclaw.mjs"
|
||||
)
|
||||
|
||||
# Also check for node_modules in common project locations
|
||||
if [[ -n "${NODE_PATH:-}" ]]; then
|
||||
openclaw_paths+=("${NODE_PATH}/openclaw/openclaw.mjs")
|
||||
fi
|
||||
|
||||
for candidate in "${openclaw_paths[@]}"; do
|
||||
if [[ -f "$candidate" ]]; then
|
||||
OPENCLAW_PATH="$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
OPENCLAW_PATH=""
|
||||
return 1
|
||||
}
|
||||
|
||||
check_openclaw() {
|
||||
if ! find_openclaw; then
|
||||
error "OpenClaw gateway not found"
|
||||
error ""
|
||||
error "The claude-mem plugin requires an OpenClaw gateway to be installed."
|
||||
error "Please install OpenClaw first:"
|
||||
error ""
|
||||
error " npm install -g openclaw"
|
||||
error " # or visit: https://openclaw.dev/docs/installation"
|
||||
error ""
|
||||
error "Then re-run this installer."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
success "OpenClaw gateway found at ${OPENCLAW_PATH}"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Plugin installation — clone, build, install, enable
|
||||
# Flow based on openclaw/Dockerfile.e2e
|
||||
###############################################################################
|
||||
|
||||
CLAUDE_MEM_REPO="https://github.com/thedotmack/claude-mem.git"
|
||||
|
||||
install_plugin() {
|
||||
local build_dir
|
||||
build_dir="$(mktemp -d)"
|
||||
|
||||
# Ensure cleanup on exit from this function
|
||||
cleanup_build_dir() {
|
||||
if [[ -d "$build_dir" ]]; then
|
||||
rm -rf "$build_dir"
|
||||
fi
|
||||
}
|
||||
trap cleanup_build_dir EXIT
|
||||
|
||||
info "Cloning claude-mem repository..."
|
||||
if ! git clone --depth 1 "$CLAUDE_MEM_REPO" "$build_dir/claude-mem" 2>&1; then
|
||||
error "Failed to clone claude-mem repository"
|
||||
error "Check your internet connection and try again."
|
||||
cleanup_build_dir
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local plugin_src="${build_dir}/claude-mem/openclaw"
|
||||
|
||||
# Build the TypeScript plugin
|
||||
info "Building TypeScript plugin..."
|
||||
if ! (cd "$plugin_src" && NODE_ENV=development npm install --ignore-scripts 2>&1 && npx tsc 2>&1); then
|
||||
error "Failed to build the claude-mem OpenClaw plugin"
|
||||
error "Make sure Node.js and npm are installed."
|
||||
cleanup_build_dir
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create minimal installable package (matches Dockerfile.e2e pattern)
|
||||
local installable_dir="${build_dir}/claude-mem-installable"
|
||||
mkdir -p "${installable_dir}/dist"
|
||||
|
||||
cp "${plugin_src}/dist/index.js" "${installable_dir}/dist/"
|
||||
cp "${plugin_src}/dist/index.d.ts" "${installable_dir}/dist/" 2>/dev/null || true
|
||||
cp "${plugin_src}/openclaw.plugin.json" "${installable_dir}/"
|
||||
|
||||
# Generate the installable package.json with openclaw.extensions field
|
||||
node -e "
|
||||
const pkg = {
|
||||
name: 'claude-mem',
|
||||
version: '1.0.0',
|
||||
type: 'module',
|
||||
main: 'dist/index.js',
|
||||
openclaw: { extensions: ['./dist/index.js'] }
|
||||
};
|
||||
require('fs').writeFileSync('${installable_dir}/package.json', JSON.stringify(pkg, null, 2));
|
||||
"
|
||||
|
||||
# Install the plugin using OpenClaw's CLI
|
||||
info "Installing claude-mem plugin into OpenClaw..."
|
||||
if ! node "$OPENCLAW_PATH" plugins install "$installable_dir" 2>&1; then
|
||||
error "Failed to install claude-mem plugin"
|
||||
error "Try manually: node ${OPENCLAW_PATH} plugins install <path>"
|
||||
cleanup_build_dir
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Enable the plugin
|
||||
info "Enabling claude-mem plugin..."
|
||||
if ! node "$OPENCLAW_PATH" plugins enable claude-mem 2>&1; then
|
||||
error "Failed to enable claude-mem plugin"
|
||||
error "Try manually: node ${OPENCLAW_PATH} plugins enable claude-mem"
|
||||
cleanup_build_dir
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cleanup_build_dir
|
||||
trap - EXIT
|
||||
success "claude-mem plugin installed and enabled"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Memory slot configuration
|
||||
# Sets plugins.slots.memory = "claude-mem" in ~/.openclaw/openclaw.json
|
||||
###############################################################################
|
||||
|
||||
configure_memory_slot() {
|
||||
local config_dir="${HOME}/.openclaw"
|
||||
local config_file="${config_dir}/openclaw.json"
|
||||
|
||||
mkdir -p "$config_dir"
|
||||
|
||||
if [[ ! -f "$config_file" ]]; then
|
||||
# No config file exists — create one with the memory slot
|
||||
info "Creating OpenClaw configuration with claude-mem memory slot..."
|
||||
node -e "
|
||||
const config = {
|
||||
plugins: {
|
||||
slots: { memory: 'claude-mem' },
|
||||
entries: {
|
||||
'claude-mem': {
|
||||
enabled: true,
|
||||
config: {
|
||||
workerPort: 37777,
|
||||
syncMemoryFile: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
||||
"
|
||||
success "Created ${config_file} with memory slot set to claude-mem"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Config file exists — update it to set the memory slot
|
||||
info "Updating OpenClaw configuration to use claude-mem memory slot..."
|
||||
|
||||
# Use node for reliable JSON manipulation
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const config = JSON.parse(fs.readFileSync('${config_file}', 'utf8'));
|
||||
|
||||
// Ensure plugins structure exists
|
||||
if (!config.plugins) config.plugins = {};
|
||||
if (!config.plugins.slots) config.plugins.slots = {};
|
||||
if (!config.plugins.entries) config.plugins.entries = {};
|
||||
|
||||
// Set memory slot to claude-mem
|
||||
config.plugins.slots.memory = 'claude-mem';
|
||||
|
||||
// Ensure claude-mem entry exists and is enabled
|
||||
if (!config.plugins.entries['claude-mem']) {
|
||||
config.plugins.entries['claude-mem'] = {
|
||||
enabled: true,
|
||||
config: {
|
||||
workerPort: 37777,
|
||||
syncMemoryFile: true
|
||||
}
|
||||
};
|
||||
} else {
|
||||
config.plugins.entries['claude-mem'].enabled = true;
|
||||
}
|
||||
|
||||
fs.writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
||||
"
|
||||
|
||||
success "Memory slot set to claude-mem in ${config_file}"
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Main
|
||||
###############################################################################
|
||||
@@ -275,7 +480,7 @@ main() {
|
||||
print_banner
|
||||
detect_platform
|
||||
|
||||
# --- Step 1: Bun ---
|
||||
# --- Step 1: Dependencies ---
|
||||
echo ""
|
||||
info "Checking dependencies..."
|
||||
echo ""
|
||||
@@ -284,13 +489,30 @@ main() {
|
||||
install_bun
|
||||
fi
|
||||
|
||||
# --- Step 2: uv ---
|
||||
if ! check_uv; then
|
||||
install_uv
|
||||
fi
|
||||
|
||||
echo ""
|
||||
success "All dependencies satisfied"
|
||||
|
||||
# --- Step 2: OpenClaw gateway ---
|
||||
echo ""
|
||||
info "Locating OpenClaw gateway..."
|
||||
check_openclaw
|
||||
|
||||
# --- Step 3: Plugin installation ---
|
||||
echo ""
|
||||
info "Installing claude-mem plugin..."
|
||||
install_plugin
|
||||
|
||||
# --- Step 4: Memory slot configuration ---
|
||||
echo ""
|
||||
info "Configuring memory slot..."
|
||||
configure_memory_slot
|
||||
|
||||
echo ""
|
||||
success "OpenClaw gateway detection and plugin installation complete"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
Executable
+329
@@ -0,0 +1,329 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Test suite for openclaw/install.sh functions
|
||||
# Tests the OpenClaw gateway detection, plugin install, and memory slot config.
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
INSTALL_SCRIPT="${SCRIPT_DIR}/install.sh"
|
||||
|
||||
TESTS_RUN=0
|
||||
TESTS_PASSED=0
|
||||
TESTS_FAILED=0
|
||||
|
||||
###############################################################################
|
||||
# Test helpers
|
||||
###############################################################################
|
||||
|
||||
test_pass() {
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
TESTS_PASSED=$((TESTS_PASSED + 1))
|
||||
echo -e "\033[0;32m✓\033[0m $1"
|
||||
}
|
||||
|
||||
test_fail() {
|
||||
TESTS_RUN=$((TESTS_RUN + 1))
|
||||
TESTS_FAILED=$((TESTS_FAILED + 1))
|
||||
echo -e "\033[0;31m✗\033[0m $1"
|
||||
if [[ -n "${2:-}" ]]; then
|
||||
echo " Detail: $2"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_eq() {
|
||||
local expected="$1" actual="$2" msg="$3"
|
||||
if [[ "$expected" == "$actual" ]]; then
|
||||
test_pass "$msg"
|
||||
else
|
||||
test_fail "$msg" "expected='${expected}' actual='${actual}'"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
local haystack="$1" needle="$2" msg="$3"
|
||||
if [[ "$haystack" == *"$needle"* ]]; then
|
||||
test_pass "$msg"
|
||||
else
|
||||
test_fail "$msg" "expected string to contain '${needle}'"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_file_exists() {
|
||||
local filepath="$1" msg="$2"
|
||||
if [[ -f "$filepath" ]]; then
|
||||
test_pass "$msg"
|
||||
else
|
||||
test_fail "$msg" "file not found: ${filepath}"
|
||||
fi
|
||||
}
|
||||
|
||||
###############################################################################
|
||||
# Source the install script without running main()
|
||||
# We override main to be a no-op, then source the file.
|
||||
###############################################################################
|
||||
|
||||
source_install_functions() {
|
||||
# Create a temp file that overrides main and sources the install script
|
||||
local tmp_source
|
||||
tmp_source="$(mktemp)"
|
||||
# Extract everything except the final `main "$@"` invocation
|
||||
sed '$ d' "$INSTALL_SCRIPT" > "$tmp_source"
|
||||
# Override main to prevent execution
|
||||
echo 'main() { :; }' >> "$tmp_source"
|
||||
# Source it (suppress color output for cleaner tests)
|
||||
TERM=dumb source "$tmp_source"
|
||||
rm -f "$tmp_source"
|
||||
}
|
||||
|
||||
source_install_functions
|
||||
|
||||
###############################################################################
|
||||
# Test: find_openclaw() — not found scenario
|
||||
###############################################################################
|
||||
|
||||
echo ""
|
||||
echo "=== find_openclaw() ==="
|
||||
|
||||
# Save original PATH and test with empty locations
|
||||
ORIGINAL_PATH="$PATH"
|
||||
ORIGINAL_HOME="$HOME"
|
||||
|
||||
test_find_openclaw_not_found() {
|
||||
# Use a fake HOME where nothing exists
|
||||
local fake_home
|
||||
fake_home="$(mktemp -d)"
|
||||
HOME="$fake_home"
|
||||
PATH="/nonexistent"
|
||||
OPENCLAW_PATH=""
|
||||
|
||||
if find_openclaw 2>/dev/null; then
|
||||
test_fail "find_openclaw should return 1 when openclaw.mjs is not found"
|
||||
else
|
||||
test_pass "find_openclaw returns 1 when not found"
|
||||
fi
|
||||
|
||||
assert_eq "" "$OPENCLAW_PATH" "OPENCLAW_PATH is empty when not found"
|
||||
|
||||
HOME="$ORIGINAL_HOME"
|
||||
PATH="$ORIGINAL_PATH"
|
||||
rm -rf "$fake_home"
|
||||
}
|
||||
|
||||
test_find_openclaw_not_found
|
||||
|
||||
# Test: find_openclaw() — found in HOME/.openclaw/
|
||||
test_find_openclaw_in_home() {
|
||||
local fake_home
|
||||
fake_home="$(mktemp -d)"
|
||||
mkdir -p "${fake_home}/.openclaw"
|
||||
touch "${fake_home}/.openclaw/openclaw.mjs"
|
||||
|
||||
HOME="$fake_home"
|
||||
PATH="/nonexistent"
|
||||
OPENCLAW_PATH=""
|
||||
|
||||
if find_openclaw 2>/dev/null; then
|
||||
test_pass "find_openclaw finds openclaw.mjs in ~/.openclaw/"
|
||||
assert_eq "${fake_home}/.openclaw/openclaw.mjs" "$OPENCLAW_PATH" "OPENCLAW_PATH set correctly"
|
||||
else
|
||||
test_fail "find_openclaw should find openclaw.mjs in ~/.openclaw/"
|
||||
fi
|
||||
|
||||
HOME="$ORIGINAL_HOME"
|
||||
PATH="$ORIGINAL_PATH"
|
||||
rm -rf "$fake_home"
|
||||
}
|
||||
|
||||
test_find_openclaw_in_home
|
||||
|
||||
###############################################################################
|
||||
# Test: configure_memory_slot() — creates new config
|
||||
###############################################################################
|
||||
|
||||
echo ""
|
||||
echo "=== configure_memory_slot() ==="
|
||||
|
||||
test_configure_new_config() {
|
||||
local fake_home
|
||||
fake_home="$(mktemp -d)"
|
||||
HOME="$fake_home"
|
||||
|
||||
configure_memory_slot >/dev/null 2>&1
|
||||
|
||||
local config_file="${fake_home}/.openclaw/openclaw.json"
|
||||
assert_file_exists "$config_file" "Config file created at ~/.openclaw/openclaw.json"
|
||||
|
||||
# Verify JSON structure
|
||||
local memory_slot
|
||||
memory_slot="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.slots.memory);")"
|
||||
assert_eq "claude-mem" "$memory_slot" "Memory slot set to claude-mem in new config"
|
||||
|
||||
local enabled
|
||||
enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].enabled);")"
|
||||
assert_eq "true" "$enabled" "claude-mem entry is enabled in new config"
|
||||
|
||||
local worker_port
|
||||
worker_port="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.workerPort);")"
|
||||
assert_eq "37777" "$worker_port" "Worker port is 37777 in new config"
|
||||
|
||||
HOME="$ORIGINAL_HOME"
|
||||
rm -rf "$fake_home"
|
||||
}
|
||||
|
||||
test_configure_new_config
|
||||
|
||||
# Test: configure_memory_slot() — updates existing config
|
||||
test_configure_existing_config() {
|
||||
local fake_home
|
||||
fake_home="$(mktemp -d)"
|
||||
HOME="$fake_home"
|
||||
|
||||
# Create an existing config with other settings
|
||||
mkdir -p "${fake_home}/.openclaw"
|
||||
local config_file="${fake_home}/.openclaw/openclaw.json"
|
||||
node -e "
|
||||
const config = {
|
||||
gateway: { mode: 'local' },
|
||||
plugins: {
|
||||
slots: { memory: 'memory-core' },
|
||||
entries: {
|
||||
'some-other-plugin': { enabled: true }
|
||||
}
|
||||
}
|
||||
};
|
||||
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
||||
"
|
||||
|
||||
configure_memory_slot >/dev/null 2>&1
|
||||
|
||||
# Verify memory slot was updated
|
||||
local memory_slot
|
||||
memory_slot="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.slots.memory);")"
|
||||
assert_eq "claude-mem" "$memory_slot" "Memory slot updated from memory-core to claude-mem"
|
||||
|
||||
# Verify existing settings preserved
|
||||
local gateway_mode
|
||||
gateway_mode="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.gateway.mode);")"
|
||||
assert_eq "local" "$gateway_mode" "Existing gateway.mode setting preserved"
|
||||
|
||||
# Verify other plugin still present
|
||||
local other_plugin
|
||||
other_plugin="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['some-other-plugin'].enabled);")"
|
||||
assert_eq "true" "$other_plugin" "Existing plugin entries preserved"
|
||||
|
||||
# Verify claude-mem entry was added
|
||||
local cm_enabled
|
||||
cm_enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].enabled);")"
|
||||
assert_eq "true" "$cm_enabled" "claude-mem entry added and enabled"
|
||||
|
||||
HOME="$ORIGINAL_HOME"
|
||||
rm -rf "$fake_home"
|
||||
}
|
||||
|
||||
test_configure_existing_config
|
||||
|
||||
# Test: configure_memory_slot() — preserves existing claude-mem config
|
||||
test_configure_preserves_existing_cm_config() {
|
||||
local fake_home
|
||||
fake_home="$(mktemp -d)"
|
||||
HOME="$fake_home"
|
||||
|
||||
mkdir -p "${fake_home}/.openclaw"
|
||||
local config_file="${fake_home}/.openclaw/openclaw.json"
|
||||
node -e "
|
||||
const config = {
|
||||
plugins: {
|
||||
slots: { memory: 'memory-core' },
|
||||
entries: {
|
||||
'claude-mem': {
|
||||
enabled: false,
|
||||
config: {
|
||||
workerPort: 38888,
|
||||
observationFeed: { enabled: true, channel: 'telegram', to: '12345' }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
require('fs').writeFileSync('${config_file}', JSON.stringify(config, null, 2));
|
||||
"
|
||||
|
||||
configure_memory_slot >/dev/null 2>&1
|
||||
|
||||
# Should enable it but preserve existing config
|
||||
local cm_enabled
|
||||
cm_enabled="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].enabled);")"
|
||||
assert_eq "true" "$cm_enabled" "claude-mem entry enabled when previously disabled"
|
||||
|
||||
local custom_port
|
||||
custom_port="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.workerPort);")"
|
||||
assert_eq "38888" "$custom_port" "Existing custom workerPort preserved"
|
||||
|
||||
local feed_channel
|
||||
feed_channel="$(node -e "const c = JSON.parse(require('fs').readFileSync('${config_file}','utf8')); console.log(c.plugins.entries['claude-mem'].config.observationFeed.channel);")"
|
||||
assert_eq "telegram" "$feed_channel" "Existing observationFeed config preserved"
|
||||
|
||||
HOME="$ORIGINAL_HOME"
|
||||
rm -rf "$fake_home"
|
||||
}
|
||||
|
||||
test_configure_preserves_existing_cm_config
|
||||
|
||||
###############################################################################
|
||||
# Test: version_gte() — already exists from phase 1
|
||||
###############################################################################
|
||||
|
||||
echo ""
|
||||
echo "=== version_gte() ==="
|
||||
|
||||
if version_gte "1.2.0" "1.1.14"; then
|
||||
test_pass "version_gte: 1.2.0 >= 1.1.14"
|
||||
else
|
||||
test_fail "version_gte: 1.2.0 >= 1.1.14"
|
||||
fi
|
||||
|
||||
if version_gte "1.1.14" "1.1.14"; then
|
||||
test_pass "version_gte: 1.1.14 >= 1.1.14 (equal)"
|
||||
else
|
||||
test_fail "version_gte: 1.1.14 >= 1.1.14 (equal)"
|
||||
fi
|
||||
|
||||
if ! version_gte "1.0.0" "1.1.14"; then
|
||||
test_pass "version_gte: 1.0.0 < 1.1.14"
|
||||
else
|
||||
test_fail "version_gte: 1.0.0 < 1.1.14"
|
||||
fi
|
||||
|
||||
###############################################################################
|
||||
# Test: Script structure validation
|
||||
###############################################################################
|
||||
|
||||
echo ""
|
||||
echo "=== Script structure ==="
|
||||
|
||||
# Verify all required functions exist
|
||||
for fn in find_openclaw check_openclaw install_plugin configure_memory_slot; do
|
||||
if declare -f "$fn" &>/dev/null; then
|
||||
test_pass "Function ${fn}() is defined"
|
||||
else
|
||||
test_fail "Function ${fn}() should be defined"
|
||||
fi
|
||||
done
|
||||
|
||||
# Verify the CLAUDE_MEM_REPO constant
|
||||
assert_contains "$CLAUDE_MEM_REPO" "github.com/thedotmack/claude-mem" "CLAUDE_MEM_REPO points to correct repository"
|
||||
|
||||
###############################################################################
|
||||
# Summary
|
||||
###############################################################################
|
||||
|
||||
echo ""
|
||||
echo "========================================"
|
||||
echo "Results: ${TESTS_PASSED}/${TESTS_RUN} passed, ${TESTS_FAILED} failed"
|
||||
echo "========================================"
|
||||
|
||||
if [[ "$TESTS_FAILED" -gt 0 ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exit 0
|
||||
Reference in New Issue
Block a user