installer

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 23, 2025 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Copyright (c) 2025 Guilherme Silva Sousa Licensed under the MIT License See LICENSE file in the project root for full license information. Package installer provides the complete command installation process for ccmd. It handles the entire installation workflow from cloning repositories to updating lock files, with proper error handling and rollback capabilities.

Copyright (c) 2025 Guilherme Silva Sousa Licensed under the MIT License See LICENSE file in the project root for full license information.

Copyright (c) 2025 Guilherme Silva Sousa Licensed under the MIT License See LICENSE file in the project root for full license information.

Copyright (c) 2025 Guilherme Silva Sousa Licensed under the MIT License See LICENSE file in the project root for full license information.

Copyright (c) 2025 Guilherme Silva Sousa Licensed under the MIT License See LICENSE file in the project root for full license information.

Example

Example demonstrates basic command installation

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	// Create installer with options
	opts := installer.Options{
		Repository: "https://github.com/example/mycmd.git",
		Version:    "v1.0.0",
		InstallDir: ".claude/commands",
	}

	inst, err := installer.New(opts)
	if err != nil {
		log.Fatal(err)
	}

	// Install the command
	ctx := context.Background()
	if err := inst.Install(ctx); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Command installed successfully")
}
Example (CommandManager)

Example_commandManager demonstrates using the CommandManager

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	// Create a command manager for the current project
	cm := installer.NewCommandManager(".")

	ctx := context.Background()

	// Install a specific command
	if err := cm.Install(ctx, "example/tool", "v1.0.0", "", false); err != nil {
		log.Fatal(err)
	}

	// List installed commands
	commands, err := cm.GetInstalledCommands()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("Installed commands:\n")
	for _, cmd := range commands {
		fmt.Printf("- %s (v%s): %s\n", cmd.Name, cmd.Version, cmd.Description)
	}
}
Example (ErrorHandling)

Example_errorHandling demonstrates error handling during installation

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	opts := installer.Options{
		Repository: "https://github.com/nonexistent/repo.git",
		InstallDir: ".claude/commands",
	}

	inst, err := installer.New(opts)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	if err := inst.Install(ctx); err != nil {
		// Check if error is retryable
		if installer.IsRetryableError(err) {
			fmt.Println("Installation failed but can be retried")
		} else {
			fmt.Println("Installation failed permanently")
		}

		// Get installation phase where error occurred
		phase := installer.GetInstallationPhase(err)
		if phase != "" {
			fmt.Printf("Error occurred during: %s\n", phase)
		}

		return
	}

	fmt.Println("Command installed successfully")
}
Example (ExtractRepoPath)

Example_extractRepoPath demonstrates extracting repository paths

package main

import (
	"fmt"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	urls := []string{
		"https://github.com/user/repo.git",
		"git@github.com:user/repo.git",
		"https://gitlab.com/org/project.git",
	}

	for _, url := range urls {
		path := installer.ExtractRepoPath(url)
		fmt.Printf("%s -> %s\n", url, path)
	}

}
Output:
https://github.com/user/repo.git -> user/repo
git@github.com:user/repo.git -> user/repo
https://gitlab.com/org/project.git -> org/project
Example (ForceReinstall)

Example_forceReinstall demonstrates force reinstallation

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	opts := installer.Options{
		Repository: "https://github.com/example/mycmd.git",
		Version:    "v2.0.0",
		Force:      true, // Overwrite existing installation
		InstallDir: ".claude/commands",
	}

	inst, err := installer.New(opts)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	if err := inst.Install(ctx); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Command reinstalled with version v2.0.0")
}
Example (InstallFromConfig)

Example_installFromConfig demonstrates installing all commands from ccmd.yaml

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	ctx := context.Background()
	projectPath := "." // Current directory

	// Install all commands defined in ccmd.yaml
	if err := installer.InstallFromConfig(ctx, projectPath, false); err != nil {
		log.Fatal(err)
	}

	fmt.Println("All commands from ccmd.yaml installed")
}
Example (InstallFromShorthand)

Example_installFromShorthand demonstrates GitHub shorthand installation

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	// Use the integration function for shorthand support
	ctx := context.Background()

	opts := installer.IntegrationOptions{
		Repository: "user/repo", // GitHub shorthand
		Version:    "latest",
	}

	if err := installer.InstallCommand(ctx, opts, true); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Command installed from user/repo")
}
Example (NormalizeRepositoryURL)

Example_normalizeRepositoryURL demonstrates URL normalization

package main

import (
	"fmt"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	urls := []string{
		"user/repo",
		"github.com/user/repo",
		"https://github.com/user/repo",
		"git@github.com:user/repo.git",
	}

	for _, url := range urls {
		normalized := installer.NormalizeRepositoryURL(url)
		fmt.Printf("%s -> %s\n", url, normalized)
	}

}
Output:
user/repo -> https://github.com/user/repo.git
github.com/user/repo -> https://github.com/user/repo.git
https://github.com/user/repo -> https://github.com/user/repo.git
git@github.com:user/repo.git -> git@github.com:user/repo.git
Example (ParseRepositorySpec)

Example_parseRepositorySpec demonstrates parsing repository specifications

package main

import (
	"fmt"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	specs := []string{
		"user/repo",
		"user/repo@v1.0.0",
		"https://github.com/user/repo.git@feature/branch",
		"git@github.com:user/repo.git",
	}

	for _, spec := range specs {
		repo, version := installer.ParseRepositorySpec(spec)
		if version == "" {
			fmt.Printf("%s -> repo: %s, version:\n", spec, repo)
		} else {
			fmt.Printf("%s -> repo: %s, version: %s\n", spec, repo, version)
		}
	}

}
Output:
user/repo -> repo: user/repo, version:
user/repo@v1.0.0 -> repo: user/repo, version: v1.0.0
https://github.com/user/repo.git@feature/branch -> repo: https://github.com/user/repo.git, version: feature/branch
git@github.com:user/repo.git -> repo: git@github.com:user/repo.git, version:
Example (WithCustomName)

Example_withCustomName demonstrates installation with a custom command name

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/gifflet/ccmd/internal/installer"
)

func main() {
	opts := installer.Options{
		Repository: "https://github.com/example/long-command-name.git",
		Name:       "lcn", // Short alias
		InstallDir: ".claude/commands",
	}

	inst, err := installer.New(opts)
	if err != nil {
		log.Fatal(err)
	}

	ctx := context.Background()
	if err := inst.Install(ctx); err != nil {
		log.Fatal(err)
	}

	fmt.Println("Command installed as 'lcn'")
}

Index

Examples

Constants

View Source
const (
	ErrMsgRepositoryNotFound = "repository not found or inaccessible"
	ErrMsgInvalidRepository  = "invalid repository structure"
	ErrMsgMissingMetadata    = "ccmd.yaml not found in repository"
	ErrMsgInvalidMetadata    = "invalid ccmd.yaml format"
	ErrMsgCommandExists      = "command already exists"
	ErrMsgInstallationFailed = "installation failed"
	ErrMsgRollbackFailed     = "failed to rollback installation"
	ErrMsgVersionNotFound    = "specified version not found"
	ErrMsgNoVersionAvailable = "no version information available"
	ErrMsgPermissionDenied   = "permission denied"
	ErrMsgDiskFull           = "insufficient disk space"
	ErrMsgNetworkError       = "network error during download"
	ErrMsgInvalidCommandName = "invalid command name"
	ErrMsgLockFileCorrupted  = "lock file is corrupted"
	ErrMsgConcurrentInstall  = "another installation is in progress"
)

Common installation error messages

View Source
const (
	PhaseValidation   = "validation"
	PhaseClone        = "clone"
	PhaseVerification = "verification"
	PhaseInstall      = "install"
	PhaseMetadata     = "metadata"
	PhaseLockFile     = "lock_file"
	PhaseCleanup      = "cleanup"
	PhaseRollback     = "rollback"
)

Installation phases

Variables

This section is empty.

Functions

func ExtractRepoPath

func ExtractRepoPath(gitURL string) string

ExtractRepoPath extracts owner/repo from a Git URL

func GetInstallationPhase

func GetInstallationPhase(err error) string

GetInstallationPhase extracts the installation phase from an error

func InstallCommand

func InstallCommand(ctx context.Context, opts IntegrationOptions, addToConfig bool) error

InstallCommand performs an integrated installation with project management

func InstallFromConfig

func InstallFromConfig(ctx context.Context, projectPath string, force bool) error

InstallFromConfig installs all commands defined in a project's ccmd.yaml

func IsRetryableError

func IsRetryableError(err error) bool

IsRetryableError checks if an installation error is retryable

func NormalizeRepositoryURL

func NormalizeRepositoryURL(url string) string

NormalizeRepositoryURL normalizes various repository formats to a full URL

func ParseRepositorySpec

func ParseRepositorySpec(spec string) (repository, version string)

ParseRepositorySpec parses a repository specification (URL[@version])

func WrapInstallationError

func WrapInstallationError(err error, code errors.ErrorCode, repository, version, phase string) error

WrapInstallationError wraps an error with installation context

Types

type CommandManager

type CommandManager struct {
	// contains filtered or unexported fields
}

CommandManager provides high-level command management operations

func NewCommandManager

func NewCommandManager(projectPath string) *CommandManager

NewCommandManager creates a new command manager

func (*CommandManager) GetInstalledCommands

func (cm *CommandManager) GetInstalledCommands() ([]InstalledCommand, error)

GetInstalledCommands returns a list of installed commands

func (*CommandManager) Install

func (cm *CommandManager) Install(ctx context.Context, repository, version, name string, force bool) error

Install installs a command with the given options

func (*CommandManager) InstallFromProject

func (cm *CommandManager) InstallFromProject(ctx context.Context, force bool) error

InstallFromProject installs all commands from the project's ccmd.yaml

type GitClient

type GitClient interface {
	Clone(opts git.CloneOptions) error
	ValidateRemoteRepository(url string) error
	GetLatestTag(repoPath string) (string, error)
	GetCurrentCommit(repoPath string) (string, error)
	IsGitRepository(path string) bool
}

GitClient defines the interface for git operations

type InstallationError

type InstallationError struct {
	// contains filtered or unexported fields
}

InstallationError represents an error that occurred during installation

func NewInstallationError

func NewInstallationError(code errors.ErrorCode, message, repository, version, phase string) *InstallationError

NewInstallationError creates a new installation error

func (*InstallationError) Error

func (e *InstallationError) Error() string

Error implements the error interface

func (*InstallationError) GetCode

func (e *InstallationError) GetCode() errors.ErrorCode

GetCode returns the error code

func (*InstallationError) Phase

func (e *InstallationError) Phase() string

Phase returns the installation phase where the error occurred

func (*InstallationError) Repository

func (e *InstallationError) Repository() string

Repository returns the repository that was being installed

func (*InstallationError) Unwrap

func (e *InstallationError) Unwrap() error

Unwrap returns the underlying error

func (*InstallationError) Version

func (e *InstallationError) Version() string

Version returns the version that was being installed

type InstalledCommand

type InstalledCommand struct {
	Name        string
	Version     string
	Description string
	Author      string
	Repository  string
	Path        string
}

InstalledCommand represents an installed command

type Installer

type Installer struct {
	// contains filtered or unexported fields
}

Installer manages the command installation process

func New

func New(opts Options) (*Installer, error)

New creates a new installer instance

func (*Installer) Install

func (i *Installer) Install(_ context.Context) error

Install performs the complete installation process

type IntegrationOptions

type IntegrationOptions struct {
	Repository    string // Git repository URL or shorthand (e.g., "user/repo")
	Version       string // Version/tag to install
	Name          string // Override command name
	Force         bool   // Force reinstall
	ProjectPath   string // Path to project (for ccmd.yaml updates)
	GlobalInstall bool   // Install globally vs project-local
}

IntegrationOptions provides options for integrated installation

type MultiValidationError

type MultiValidationError struct {
	Errors []ValidationError
}

MultiValidationError represents multiple validation errors

func (*MultiValidationError) Add

func (e *MultiValidationError) Add(field, message string)

Add adds a validation error

func (*MultiValidationError) Error

func (e *MultiValidationError) Error() string

Error implements the error interface

func (*MultiValidationError) HasErrors

func (e *MultiValidationError) HasErrors() bool

HasErrors returns true if there are any validation errors

type Options

type Options struct {
	Repository    string        // Git repository URL
	Version       string        // Version/tag to install (optional)
	Name          string        // Override command name (optional)
	Force         bool          // Force reinstall if already exists
	InstallDir    string        // Directory to install commands (default: .claude/commands)
	FileSystem    fs.FileSystem // File system interface (for testing)
	GitClient     GitClient     // Git client interface (for testing)
	TempDirPrefix string        // Prefix for temporary directories
	ProjectPath   string        // Path to project root (where ccmd.yaml lives)
}

Options represents configuration for the installer

type ValidationError

type ValidationError struct {
	Field   string
	Message string
}

ValidationError represents a validation error during installation

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL