Skip to content

🚀 Smart Installer

Stack "progress callback + auto retry + context cancellation + post-install verification + system diagnostics" on top of basic installer — making install process observable, controllable, self-healing.

Package path: github.com/scagogogo/composer-skills/pkg/installer

Core Types

InstallProgress

Single progress event, passed to ProgressCallback.

go
type InstallProgress struct {
    Stage     InstallStage `json:"stage"`      // Current stage
    Message   string       `json:"message"`    // Progress message
    Percent   int          `json:"percent"`    // Percentage 0-100
    Error     error        `json:"error,omitempty"` // Error (if any)
    Timestamp time.Time    `json:"timestamp"`  // Timestamp
}

InstallStage

Install stage enum, used throughout the entire flow.

go
type InstallStage string

const (
    StageCheckingPHP     InstallStage = "checking_php"
    StageInstallingPHP   InstallStage = "installing_php"
    StageDetectingDistro InstallStage = "detecting_distro"
    StagePackageManager  InstallStage = "package_manager"
    StageDownloading     InstallStage = "downloading"
    StageInstalling      InstallStage = "installing"
    StageVerifying       InstallStage = "verifying"
    StageConfiguring     InstallStage = "configuring"
    StageCompleted       InstallStage = "completed"
    StageFailed          InstallStage = "failed"
)

ProgressCallback

Progress callback function type, called when install stage changes.

go
type ProgressCallback func(progress InstallProgress)

InstallOptions

SmartInstaller's complete input parameters.

go
type InstallOptions struct {
    Config            Config           // Installer config (fallback to SmartConfig when empty)
    MaxRetries        int              // Max retry count
    RetryDelay        time.Duration    // Retry interval
    ProgressCallback  ProgressCallback // Progress callback
    Context           context.Context  // Context for cancellation
    SkipVerification  bool             // Whether to skip post-install verification
    OnPHPInstalled    func()           // Callback after PHP installation complete
}

InstallResult

Install result, returned whether success or failure.

go
type InstallResult struct {
    Success      bool                       `json:"success"`
    ComposerPath string                     `json:"composer_path,omitempty"`
    Version      string                     `json:"version,omitempty"`
    PHPVersion   string                     `json:"php_version,omitempty"`
    Method       string                     `json:"method,omitempty"` // package_manager / homebrew / direct_download / already_installed
    Duration     time.Duration              `json:"duration,omitempty"`
    Error        error                      `json:"error,omitempty"`
    Stages       map[InstallStage]time.Duration `json:"stages,omitempty"` // Each stage duration
}

SmartInstaller

Smart installer main body.

go
type SmartInstaller struct {
    options InstallOptions
    mu      sync.Mutex
}

func NewSmartInstaller(options InstallOptions) *SmartInstaller

NewSmartInstaller reasonably fills empty fields:

FieldWhen Empty, Fills To
Config.InstallPathEntire Config fallback to SmartConfig()
MaxRetries <= 03
RetryDelay <= 05 * time.Second
Context == nilcontext.Background()

Install Stage Flow

InstallWithProgress splits install into several stages, each stage reports percentage via ProgressCallback:

Stage1: checking_php (5%)
  └─ No PHP and AutoInstallPHP=true → installing_php (10%)
       └─ DetectLinuxDistro + InstallPHP + HasPHP verify again
       └─ Trigger OnPHPInstalled callback on success
  └─ No PHP and AutoInstallPHP=false → error StageFailed return
  └─ Record PHPVersion, record checking_php stage duration

Stage2: Loop retry install Composer (max MaxRetries times)
  Each attempt:
    ├─ detecting_distro (20% + attempt*5%)  Detect system environment
    └─ doInstall(attempt):
         ├─ package_manager (30%)  Try package manager when PreferPackageManager=true
         ├─ package_manager (40%)  Try Homebrew when PreferBrewOnMac=true
         ├─ downloading (50%)      Download Composer installer
         └─ installing (70%)       Execute install
    Success → Record Method, break loop
    Failure → StageFailed (0%), wait RetryDelay then retry (cancel if Context cancelled)

Stage3: verifying (90%)  When SkipVerification=false
  └─ findComposerBinary find composer → Record ComposerPath
  └─ CheckComposerVersion → Record Version
  └─ Record verifying stage duration

Complete: completed (100%)
  └─ Success=true, record total Duration, return InstallResult

Stage Percentage Meaning

Percentages are "progress visualization" hint values, not strict engineering progress. On retry, percentage increments with attempt (20 + attempt*5), failed retry stage goes back to 0.

Method Signatures

DefaultInstallOptions

Return default install options (SmartConfig + 3 retries + 5s interval + context.Background).

go
func DefaultInstallOptions() InstallOptions

NewSmartInstaller

Create smart installer, fill empty fields.

go
func NewSmartInstaller(options InstallOptions) *SmartInstaller

InstallWithProgress

Execute smart install with progress report.

go
func (si *SmartInstaller) InstallWithProgress() (*InstallResult, error)

Return value: *InstallResult always non-nil (contains Success/Error/Stages etc. diagnostic info), error is occurred error (nil on success). InstallResult.Error and returned error are consistent on failure.

EnsureComposerInstalled

Convenience method: already installed returns directly, not installed smart installs.

go
func EnsureComposerInstalled(options *InstallOptions) (*InstallResult, error)
  • Already installed: Method="already_installed", fills ComposerPath/Version/PHPVersion`, doesn't execute any install action.
  • Not installed: options nil uses DefaultInstallOptions(), otherwise uses passed options, calls NewSmartInstaller(opts).InstallWithProgress().

IsComposerInstalled

Check if Composer is installed, get path and version in one step.

go
func IsComposerInstalled() (bool, string, string)

Returns: (whether installed, install path, version number). Path and version are empty strings when not installed.

GetSystemInfo

Collect system info for diagnosing install issues.

go
func GetSystemInfo() map[string]string

Returned map contains keys:

KeyDescription
php_availabletrue/false
php_versionPHP version (when available)
composer_availabletrue/false
composer_pathComposer path (when available)
composer_versionComposer version (when available)
distro_id / distro_name / distro_version / package_managerLinux distro info (Linux only)
brew_availabletrue/false
curl_availabletrue/false

Quick Examples

Install with Progress Bar

go
package main

import (
    "fmt"
    "log"

    "github.com/scagogogo/composer-skills/pkg/installer"
)

func main() {
    opts := installer.DefaultInstallOptions()
    opts.ProgressCallback = func(p installer.InstallProgress) {
        fmt.Printf("[%s] %s (%d%%)\n", p.Stage, p.Message, p.Percent)
        if p.Error != nil {
            fmt.Printf("  ⚠️ Error: %v\n", p.Error)
        }
    }

    si := installer.NewSmartInstaller(opts)
    result, err := si.InstallWithProgress()
    if err != nil {
        log.Fatalf("Installation failed: %v", err)
    }
    fmt.Printf("✅ Composer %s installed successfully, path: %s, duration: %v\n",
        result.Version, result.ComposerPath, result.Duration)
}

Cancellable Install

go
package main

import (
    "context"
    "log"
    "time"

    "github.com/scagogogo/composer-skills/pkg/installer"
)

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
    defer cancel()

    opts := installer.DefaultInstallOptions()
    opts.Context = ctx

    si := installer.NewSmartInstaller(opts)
    result, err := si.InstallWithProgress()
    if err != nil {
        log.Fatalf("Installation failed or cancelled: %v", err)
    }
    _ = result
}

One-line Ensure Installed

go
result, err := installer.EnsureComposerInstalled(nil)
if err != nil {
    log.Fatal(err)
}
fmt.Println("Composer path:", result.ComposerPath, "method:", result.Method)

Diagnose System Environment

go
info := installer.GetSystemInfo()
for k, v := range info {
    fmt.Printf("%s = %s\n", k, v)
}

Advanced

  • Method field semantics: package_manager (Linux package manager install), homebrew (macOS brew install), direct_download (direct download setup.php), already_installed (EnsureComposerInstalled found already installed).
  • Stages duration: InstallResult.Stages records checking_php and verifying two stages' time.Duration, useful for performance analysis. Other stages not individually timed.
  • PHP install callback: OnPHPInstalled triggers immediately after PHP auto-install success and HasPHP verification, suitable for logging or notifying user "PHP ready, starting Composer install".
  • SkipVerification: Set true to skip stage 3, InstallResult.ComposerPath and Version will be empty — only use when confident install must succeed and no version info needed.
  • Integration with Composer: pkg/composer's EnsureInstalledWithProgress(callback) and QuickSetupWithProgress internally call this package's EnsureComposerInstalled / SmartInstaller, passing install progress to upper layer.

Released under the MIT License