531 lines
16 KiB
Plaintext
531 lines
16 KiB
Plaintext
|
|
#!/usr/bin/env nu
|
|||
|
|
# install-with-presets.nu - Local syntaxis installer with preset reference
|
|||
|
|
#
|
|||
|
|
# This script handles LOCAL installation and setup only.
|
|||
|
|
# For remote/distributed deployments, use project-provisioning:
|
|||
|
|
# https://github.com/Akasha/project-provisioning
|
|||
|
|
#
|
|||
|
|
# This script:
|
|||
|
|
# - Installs syntaxis binaries locally
|
|||
|
|
# - Configures local services (optional)
|
|||
|
|
# - Shows preset templates (reference only)
|
|||
|
|
#
|
|||
|
|
# Architecture:
|
|||
|
|
# syntaxis: Binary distribution + LOCAL installation
|
|||
|
|
# project-provisioning: Remote infrastructure + multi-host orchestration
|
|||
|
|
#
|
|||
|
|
# USAGE:
|
|||
|
|
# nu scripts/provisioning/install-with-presets.nu --preset local
|
|||
|
|
# nu scripts/provisioning/install-with-presets.nu --list-presets
|
|||
|
|
# nu scripts/provisioning/install-with-presets.nu help
|
|||
|
|
|
|||
|
|
def main [
|
|||
|
|
--preset: string = "local"
|
|||
|
|
--verbose: bool = false
|
|||
|
|
--list-presets: bool = false
|
|||
|
|
...args
|
|||
|
|
] {
|
|||
|
|
# Handle help and list commands
|
|||
|
|
let first_arg = ($args | first)
|
|||
|
|
if ($first_arg == "help") or ($first_arg == "--help") or ($first_arg == "-h") {
|
|||
|
|
print_help
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if $list_presets or ($first_arg == "list-presets") {
|
|||
|
|
list_available_presets
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_header
|
|||
|
|
|
|||
|
|
# Only allow LOCAL presets that make sense for this machine
|
|||
|
|
let allowed_local_presets = ["minimal", "local", "dev"]
|
|||
|
|
|
|||
|
|
if not ($preset in $allowed_local_presets) {
|
|||
|
|
print "❌ Error: preset '$preset' is not supported for local installation"
|
|||
|
|
print ""
|
|||
|
|
print "For remote deployments (staging, production, kubernetes),"
|
|||
|
|
print "use project-provisioning:"
|
|||
|
|
print " https://github.com/Akasha/project-provisioning"
|
|||
|
|
print ""
|
|||
|
|
print "Allowed LOCAL presets:"
|
|||
|
|
$allowed_local_presets | each { |p| print $" • ($p)" }
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print_divider
|
|||
|
|
print $"Selected preset: (ansi cyan)($preset)(ansi reset)"
|
|||
|
|
|
|||
|
|
# Load preset configuration (for reference)
|
|||
|
|
let install_config = load_preset_reference $preset
|
|||
|
|
|
|||
|
|
if ($install_config == null) {
|
|||
|
|
print $"❌ Unknown preset: ($preset)"
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Show preset information
|
|||
|
|
print_preset_info $preset $install_config
|
|||
|
|
|
|||
|
|
# Pre-flight checks (minimal)
|
|||
|
|
print_divider
|
|||
|
|
print "✓ Running pre-flight checks..."
|
|||
|
|
let checks_passed = (run_preflight_checks_local $preset)
|
|||
|
|
|
|||
|
|
if not $checks_passed {
|
|||
|
|
print "⚠️ Some checks failed - proceed anyway? (y/n)"
|
|||
|
|
# For automation, continue anyway
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Check provctl availability
|
|||
|
|
print_divider
|
|||
|
|
print "🔍 Checking environment..."
|
|||
|
|
let provctl_info = (detect_provctl_availability)
|
|||
|
|
let provctl_available = ($provctl_info.available)
|
|||
|
|
|
|||
|
|
if $verbose {
|
|||
|
|
print $" provctl available: ($provctl_available)"
|
|||
|
|
if $provctl_available {
|
|||
|
|
print $" provctl path: ($provctl_info.path)"
|
|||
|
|
print $" provctl version: ($provctl_info.version)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Determine provctl usage
|
|||
|
|
let use_provctl = match $provctl {
|
|||
|
|
"required" => {
|
|||
|
|
if not $provctl_available {
|
|||
|
|
print "❌ provctl is required but not available"
|
|||
|
|
print "Install from: https://github.com/Akasha/provctl"
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
true
|
|||
|
|
}
|
|||
|
|
"disabled" => false
|
|||
|
|
"auto" => {
|
|||
|
|
if $provctl_available and ($preset_config.provctl_recommended? ?? false) {
|
|||
|
|
true
|
|||
|
|
} else {
|
|||
|
|
false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_ => false
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if $use_provctl {
|
|||
|
|
print "✓ provctl will be used for service management"
|
|||
|
|
} else {
|
|||
|
|
print "ℹ️ Service management: manual setup (generate scripts)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Installation phase
|
|||
|
|
print_divider
|
|||
|
|
print "📦 Installing syntaxis binaries..."
|
|||
|
|
|
|||
|
|
# Determine which binaries to install based on preset
|
|||
|
|
let binaries_to_install = (get_binaries_for_preset $effective_preset $preset_config)
|
|||
|
|
|
|||
|
|
if ($binaries_to_install | length) == 0 {
|
|||
|
|
print "ℹ️ No binaries to install for this preset"
|
|||
|
|
} else {
|
|||
|
|
# Call the original installer
|
|||
|
|
print $"Installing ($binaries_to_install | length) binary(ies)..."
|
|||
|
|
try {
|
|||
|
|
# Run original installer for binaries
|
|||
|
|
nu scripts/install-syntaxis.nu all 2>/dev/null
|
|||
|
|
print "✓ Binaries installed"
|
|||
|
|
} catch { |err|
|
|||
|
|
print $"⚠️ Binary installation had issues: ($err)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Service setup phase
|
|||
|
|
let services_config = ($preset_config.services?)
|
|||
|
|
if ($services_config != null) {
|
|||
|
|
print_divider
|
|||
|
|
print "🚀 Setting up services..."
|
|||
|
|
setup_services $effective_preset $services_config $use_provctl $provctl_info
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Database configuration
|
|||
|
|
print_divider
|
|||
|
|
print "🗄️ Configuring database..."
|
|||
|
|
let db_backend = ($preset_config.database_backend? ?? "sqlite")
|
|||
|
|
setup_database $db_backend $effective_preset
|
|||
|
|
|
|||
|
|
# Create manifest
|
|||
|
|
print_divider
|
|||
|
|
print "📋 Creating installation manifest..."
|
|||
|
|
create_installation_manifest $effective_preset $preset_config $use_provctl
|
|||
|
|
|
|||
|
|
# Summary and next steps
|
|||
|
|
print_divider
|
|||
|
|
print_installation_summary $effective_preset $preset_config
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Load configuration from file
|
|||
|
|
def load_config_from_file [config_path: string] {
|
|||
|
|
if not ($config_path | path exists) {
|
|||
|
|
print $"⚠️ Configuration not found: ($config_path)"
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
open $config_path
|
|||
|
|
} catch { |err|
|
|||
|
|
print $"❌ Failed to load configuration: ($err)"
|
|||
|
|
return null
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Generate installation config output
|
|||
|
|
def generate_installation_config [preset: string, config: record]: nothing -> string {
|
|||
|
|
let output = $"# Generated installation configuration for preset: ($preset)
|
|||
|
|
[installation]
|
|||
|
|
preset = \"($preset)\"
|
|||
|
|
interactive = false
|
|||
|
|
|
|||
|
|
[database]
|
|||
|
|
backend = \"($config.database_backend?)\"
|
|||
|
|
|
|||
|
|
[services]
|
|||
|
|
"
|
|||
|
|
|
|||
|
|
# Add services
|
|||
|
|
let services_str = (
|
|||
|
|
if ($config.services? != null) {
|
|||
|
|
($config.services | to toml)
|
|||
|
|
} else {
|
|||
|
|
""
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
$output + $services_str
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Detect provctl availability
|
|||
|
|
def detect_provctl_availability []: nothing -> record {
|
|||
|
|
try {
|
|||
|
|
nu scripts/provisioning/detect-provctl.nu
|
|||
|
|
} catch {
|
|||
|
|
{
|
|||
|
|
available: false,
|
|||
|
|
path: null,
|
|||
|
|
version: null,
|
|||
|
|
commands: [],
|
|||
|
|
backends: []
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Select preset interactively
|
|||
|
|
def select_preset_interactive [config: record]: nothing -> string {
|
|||
|
|
let available_presets = ($config | get preset | keys)
|
|||
|
|
|
|||
|
|
if ($available_presets | length) == 0 {
|
|||
|
|
return "local"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print ""
|
|||
|
|
print "Available installation presets:"
|
|||
|
|
print ""
|
|||
|
|
|
|||
|
|
for (idx, preset) in ($available_presets | enumerate) {
|
|||
|
|
let preset_info = ($config.preset | get $preset)
|
|||
|
|
let name = ($preset_info.name? ?? $preset)
|
|||
|
|
let desc = ($preset_info.description? ?? "")
|
|||
|
|
|
|||
|
|
print $" ($idx + 1)) ($name)"
|
|||
|
|
if ($desc != "") {
|
|||
|
|
print $" ($desc)"
|
|||
|
|
}
|
|||
|
|
print ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Get user input
|
|||
|
|
let choice = (input "Select preset (1-$(($available_presets | length)): ")
|
|||
|
|
let choice_num = (try { $choice | into int } catch { 1 })
|
|||
|
|
|
|||
|
|
if ($choice_num > 0) and ($choice_num <= ($available_presets | length)) {
|
|||
|
|
$available_presets | get ($choice_num - 1)
|
|||
|
|
} else {
|
|||
|
|
"local"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Get binaries for preset
|
|||
|
|
def get_binaries_for_preset [preset: string, config: record]: nothing -> list {
|
|||
|
|
["syntaxis-cli", "syntaxis-tui"] # Core binaries always installed
|
|||
|
|
# API and others conditional on preset
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Setup database
|
|||
|
|
def setup_database [backend: string, preset: string] {
|
|||
|
|
print $"Database backend: (ansi cyan)($backend)(ansi reset)"
|
|||
|
|
|
|||
|
|
match $backend {
|
|||
|
|
"sqlite" => {
|
|||
|
|
let db_path = "~/.local/share/core/workspace.db"
|
|||
|
|
print $" Location: ($db_path)"
|
|||
|
|
print $" Status: File-based (automatic)"
|
|||
|
|
}
|
|||
|
|
"surrealdb" => {
|
|||
|
|
print $" Mode: Check configs/database-surrealdb.toml"
|
|||
|
|
print $" Status: Requires configuration"
|
|||
|
|
}
|
|||
|
|
_ => {
|
|||
|
|
print $" Unknown backend: ($backend)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Setup services
|
|||
|
|
def setup_services [preset: string, services: record, use_provctl: bool, provctl_info: record] {
|
|||
|
|
print $"Services for preset: (ansi cyan)($preset)(ansi reset)"
|
|||
|
|
|
|||
|
|
let service_list = []
|
|||
|
|
|
|||
|
|
# Iterate through services
|
|||
|
|
try {
|
|||
|
|
$services | transpose | each { |item|
|
|||
|
|
let service_name = $item.key
|
|||
|
|
let service_config = $item.value
|
|||
|
|
|
|||
|
|
if ($service_config.enabled? ?? false) {
|
|||
|
|
print $" ✓ ($service_name)"
|
|||
|
|
if ($service_config.auto_start? ?? false) {
|
|||
|
|
print $" └─ auto-start: enabled"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
print " (no services configured)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if $use_provctl {
|
|||
|
|
print ""
|
|||
|
|
print "📌 Service deployment with provctl:"
|
|||
|
|
print " Run: nu scripts/provisioning/provctl-services.nu deploy $preset"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Create installation manifest
|
|||
|
|
def create_installation_manifest [preset: string, config: record, used_provctl: bool] {
|
|||
|
|
let manifest_path = ".syntaxis/manifest-installation.toml"
|
|||
|
|
let timestamp = (date now | format date "%Y-%m-%d %H:%M:%S")
|
|||
|
|
|
|||
|
|
let manifest = $"# Installation Manifest
|
|||
|
|
[installation]
|
|||
|
|
preset = \"($preset)\"
|
|||
|
|
timestamp = \"($timestamp)\"
|
|||
|
|
provctl_used = ($used_provctl)
|
|||
|
|
config_location = \"configs/installation.toml\"
|
|||
|
|
|
|||
|
|
[preset_info]
|
|||
|
|
name = \"($config.name?)\"
|
|||
|
|
description = \"($config.description?)\"
|
|||
|
|
|
|||
|
|
[database]
|
|||
|
|
backend = \"($config.database_backend?)\"
|
|||
|
|
|
|||
|
|
[notes]
|
|||
|
|
# To view services: nu scripts/provisioning/provctl-services.nu list $preset
|
|||
|
|
# To manage services: nu scripts/provisioning/provctl-services.nu deploy $preset
|
|||
|
|
"
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
^mkdir -p .syntaxis
|
|||
|
|
echo $manifest | save $manifest_path
|
|||
|
|
print $"✓ Manifest saved: ($manifest_path)"
|
|||
|
|
} catch { |err|
|
|||
|
|
print $"⚠️ Failed to create manifest: ($err)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Print header
|
|||
|
|
def print_header [] {
|
|||
|
|
print ""
|
|||
|
|
print "┌────────────────────────────────────────────────┐"
|
|||
|
|
print "│ syntaxis Enhanced Installer with provctl │"
|
|||
|
|
print "│ Smart presets + Service Management │"
|
|||
|
|
print "└────────────────────────────────────────────────┘"
|
|||
|
|
print ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Print divider
|
|||
|
|
def print_divider [] {
|
|||
|
|
print "─────────────────────────────────────────────────"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Print preset info
|
|||
|
|
def print_preset_info [name: string, config: record] {
|
|||
|
|
print $"Preset: (ansi cyan)($name)(ansi reset)"
|
|||
|
|
print $" Name: ($config.name?)"
|
|||
|
|
print $" Description: ($config.description?)"
|
|||
|
|
print $" Database: ($config.database_backend?)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Print installation summary
|
|||
|
|
def print_installation_summary [preset: string, config: record] {
|
|||
|
|
print "Installation complete!"
|
|||
|
|
print ""
|
|||
|
|
print "Next steps:"
|
|||
|
|
|
|||
|
|
match $preset {
|
|||
|
|
"local" => {
|
|||
|
|
print " 1. Try the CLI: syntaxis-cli init my-project"
|
|||
|
|
print " 2. Use TUI: syntaxis-tui"
|
|||
|
|
print ""
|
|||
|
|
print "To upgrade later: nu scripts/provisioning/install-with-presets.nu --preset dev"
|
|||
|
|
}
|
|||
|
|
"dev" => {
|
|||
|
|
print " 1. Start services: provctl start surrealdb && provctl start nats"
|
|||
|
|
print " 2. Or use: docker-compose up"
|
|||
|
|
print " 3. Open dashboard: http://localhost:3000"
|
|||
|
|
print " 4. Use API: curl http://localhost:3000/health"
|
|||
|
|
}
|
|||
|
|
"staging" => {
|
|||
|
|
print " 1. Start Docker services: docker-compose -f docker/docker-compose.yml up"
|
|||
|
|
print " 2. Verify health: curl http://localhost:3000/health"
|
|||
|
|
print " 3. Open dashboard: http://localhost:3000"
|
|||
|
|
print " 4. Monitor: http://localhost:9090"
|
|||
|
|
}
|
|||
|
|
"production" => {
|
|||
|
|
print " 1. Review Kubernetes manifests: ls kubernetes/"
|
|||
|
|
print " 2. Create namespace: kubectl create namespace syntaxis"
|
|||
|
|
print " 3. Deploy: kubectl apply -f kubernetes/"
|
|||
|
|
print " 4. Monitor: kubectl logs -n syntaxis deployment/syntaxis-api"
|
|||
|
|
}
|
|||
|
|
_ => {
|
|||
|
|
print " Installation complete. Run 'syntaxis-cli --help' for usage."
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print ""
|
|||
|
|
print "Documentation: docs/INSTALLATION_CONTEXTS.md"
|
|||
|
|
print ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Print help message
|
|||
|
|
def print_help [] {
|
|||
|
|
print ""
|
|||
|
|
print "syntaxis Enhanced Installer with Presets and provctl Integration"
|
|||
|
|
print ""
|
|||
|
|
print "USAGE:"
|
|||
|
|
print " nu scripts/provisioning/install-with-presets.nu [OPTIONS]"
|
|||
|
|
print ""
|
|||
|
|
print "OPTIONS:"
|
|||
|
|
print " --preset <PRESET> Installation preset (local, minimal, dev, staging, production, custom)"
|
|||
|
|
print " --interactive Interactive mode - ask questions"
|
|||
|
|
print " --config <FILE> Load configuration from file"
|
|||
|
|
print " --generate-config Generate config file and exit"
|
|||
|
|
print " --provctl <MODE> provctl mode: auto (default), required, disabled"
|
|||
|
|
print " --verbose Verbose output"
|
|||
|
|
print " --list-presets List all available presets"
|
|||
|
|
print " -h, --help Show this help message"
|
|||
|
|
print ""
|
|||
|
|
print "EXAMPLES:"
|
|||
|
|
print " # Simple local installation"
|
|||
|
|
print " nu scripts/provisioning/install-with-presets.nu"
|
|||
|
|
print ""
|
|||
|
|
print " # Development with services"
|
|||
|
|
print " nu scripts/provisioning/install-with-presets.nu --preset dev"
|
|||
|
|
print ""
|
|||
|
|
print " # Interactive setup"
|
|||
|
|
print " nu scripts/provisioning/install-with-presets.nu --interactive"
|
|||
|
|
print ""
|
|||
|
|
print " # Generate configuration"
|
|||
|
|
print " nu scripts/provisioning/install-with-presets.nu --preset staging --generate-config"
|
|||
|
|
print ""
|
|||
|
|
print " # Install from configuration file"
|
|||
|
|
print " nu scripts/provisioning/install-with-presets.nu --config my-install.toml"
|
|||
|
|
print ""
|
|||
|
|
print "DOCUMENTATION:"
|
|||
|
|
print " docs/INSTALLATION_CONTEXTS.md - Detailed guide for each preset"
|
|||
|
|
print " CLAUDE.md - Installation commands reference"
|
|||
|
|
print ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# List all available presets
|
|||
|
|
def list_available_presets [] {
|
|||
|
|
let config = (
|
|||
|
|
try {
|
|||
|
|
open "configs/installation.toml"
|
|||
|
|
} catch {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
let presets = ($config | get preset | keys | sort)
|
|||
|
|
|
|||
|
|
print ""
|
|||
|
|
print "Available Installation Presets:"
|
|||
|
|
print ""
|
|||
|
|
|
|||
|
|
for preset in $presets {
|
|||
|
|
let preset_config = ($config.preset | get $preset)
|
|||
|
|
let name = ($preset_config.name? ?? $preset)
|
|||
|
|
let desc = ($preset_config.description? ?? "No description")
|
|||
|
|
let db = ($preset_config.database_backend? ?? "unknown")
|
|||
|
|
|
|||
|
|
print $" (ansi cyan)→(ansi reset) ($preset)"
|
|||
|
|
print $" Name: ($name)"
|
|||
|
|
print $" Description: ($desc)"
|
|||
|
|
print $" Database: ($db)"
|
|||
|
|
print ""
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Run pre-flight checks
|
|||
|
|
def run_preflight_checks [preset: string]: nothing -> bool {
|
|||
|
|
mut all_passed = true
|
|||
|
|
|
|||
|
|
# Check if Rust is installed
|
|||
|
|
if (try { which cargo } catch { null }) == null {
|
|||
|
|
print " ⚠️ Rust/Cargo not found (required for building)"
|
|||
|
|
$all_passed = false
|
|||
|
|
} else {
|
|||
|
|
print " ✓ Rust/Cargo detected"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Check if NuShell is available
|
|||
|
|
if (try { which nu } catch { null }) == null {
|
|||
|
|
print " ❌ NuShell required but not found"
|
|||
|
|
return false
|
|||
|
|
} else {
|
|||
|
|
print " ✓ NuShell detected"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Check if configs exist
|
|||
|
|
if not ("configs/installation.toml" | path exists) {
|
|||
|
|
print " ❌ configs/installation.toml not found"
|
|||
|
|
return false
|
|||
|
|
} else {
|
|||
|
|
print " ✓ Installation config found"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Preset-specific checks
|
|||
|
|
match $preset {
|
|||
|
|
"dev" | "staging" => {
|
|||
|
|
if (try { which surreal } catch { null }) == null {
|
|||
|
|
print " ⚠️ SurrealDB CLI not found (can install with: cargo install surrealdb-cli)"
|
|||
|
|
# Not fatal, warn but continue
|
|||
|
|
} else {
|
|||
|
|
print " ✓ SurrealDB detected"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
"staging" | "production" => {
|
|||
|
|
if (try { which docker } catch { null }) == null {
|
|||
|
|
print " ⚠️ Docker not found (required for this preset)"
|
|||
|
|
$all_passed = false
|
|||
|
|
} else {
|
|||
|
|
print " ✓ Docker detected"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
_ => {}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$all_passed
|
|||
|
|
}
|