refactor(cmd): migrate CLI from clir to cobra
Replace leaanthony/clir with spf13/cobra across all command packages. This provides better subcommand handling, built-in shell completion, and a more widely-used CLI framework. Changes: - Update cmd/core.go with cobra root command and completion support - Convert all subcommand packages to use *cobra.Command - Use init() functions for flag registration instead of inline setup - Maintain all existing functionality and flag behaviors Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6d8edeb89c
commit
a2bad1c0aa
58 changed files with 3258 additions and 2719 deletions
|
|
@ -5,7 +5,7 @@ package ai
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared package
|
// Style aliases from shared package
|
||||||
|
|
@ -53,7 +53,7 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddAgenticCommands adds the agentic task management commands to the ai command.
|
// AddAgenticCommands adds the agentic task management commands to the ai command.
|
||||||
func AddAgenticCommands(parent *clir.Command) {
|
func AddAgenticCommands(parent *cobra.Command) {
|
||||||
// Task listing and viewing
|
// Task listing and viewing
|
||||||
addTasksCommand(parent)
|
addTasksCommand(parent)
|
||||||
addTaskCommand(parent)
|
addTaskCommand(parent)
|
||||||
|
|
|
||||||
153
cmd/ai/ai_git.go
153
cmd/ai/ai_git.go
|
|
@ -12,47 +12,44 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/agentic"
|
"github.com/host-uk/core/pkg/agentic"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addTaskCommitCommand(parent *clir.Command) {
|
// task:commit command flags
|
||||||
var message string
|
var (
|
||||||
var scope string
|
taskCommitMessage string
|
||||||
var push bool
|
taskCommitScope string
|
||||||
|
taskCommitPush bool
|
||||||
|
)
|
||||||
|
|
||||||
cmd := parent.NewSubCommand("task:commit", "Auto-commit changes with task reference")
|
// task:pr command flags
|
||||||
cmd.LongDescription("Creates a git commit with a task reference and co-author attribution.\n\n" +
|
var (
|
||||||
"Commit message format:\n" +
|
taskPRTitle string
|
||||||
" feat(scope): description\n" +
|
taskPRDraft bool
|
||||||
"\n" +
|
taskPRLabels string
|
||||||
" Task: #123\n" +
|
taskPRBase string
|
||||||
" Co-Authored-By: Claude <noreply@anthropic.com>\n\n" +
|
)
|
||||||
"Examples:\n" +
|
|
||||||
" core ai task:commit abc123 --message 'add user authentication'\n" +
|
|
||||||
" core ai task:commit abc123 -m 'fix login bug' --scope auth\n" +
|
|
||||||
" core ai task:commit abc123 -m 'update docs' --push")
|
|
||||||
|
|
||||||
cmd.StringFlag("message", "Commit message (without task reference)", &message)
|
var taskCommitCmd = &cobra.Command{
|
||||||
cmd.StringFlag("m", "Commit message (short form)", &message)
|
Use: "task:commit [task-id]",
|
||||||
cmd.StringFlag("scope", "Scope for the commit type (e.g., auth, api, ui)", &scope)
|
Short: "Auto-commit changes with task reference",
|
||||||
cmd.BoolFlag("push", "Push changes after committing", &push)
|
Long: `Creates a git commit with a task reference and co-author attribution.
|
||||||
|
|
||||||
cmd.Action(func() error {
|
Commit message format:
|
||||||
// Find task ID from args
|
feat(scope): description
|
||||||
args := os.Args
|
|
||||||
var taskID string
|
|
||||||
for i, arg := range args {
|
|
||||||
if arg == "task:commit" && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
|
|
||||||
taskID = args[i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if taskID == "" {
|
Task: #123
|
||||||
return fmt.Errorf("task ID required")
|
Co-Authored-By: Claude <noreply@anthropic.com>
|
||||||
}
|
|
||||||
|
|
||||||
if message == "" {
|
Examples:
|
||||||
|
core ai task:commit abc123 --message 'add user authentication'
|
||||||
|
core ai task:commit abc123 -m 'fix login bug' --scope auth
|
||||||
|
core ai task:commit abc123 -m 'update docs' --push`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
taskID := args[0]
|
||||||
|
|
||||||
|
if taskCommitMessage == "" {
|
||||||
return fmt.Errorf("commit message required (--message or -m)")
|
return fmt.Errorf("commit message required (--message or -m)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,10 +72,10 @@ func addTaskCommitCommand(parent *clir.Command) {
|
||||||
// Build commit message with optional scope
|
// Build commit message with optional scope
|
||||||
commitType := inferCommitType(task.Labels)
|
commitType := inferCommitType(task.Labels)
|
||||||
var fullMessage string
|
var fullMessage string
|
||||||
if scope != "" {
|
if taskCommitScope != "" {
|
||||||
fullMessage = fmt.Sprintf("%s(%s): %s", commitType, scope, message)
|
fullMessage = fmt.Sprintf("%s(%s): %s", commitType, taskCommitScope, taskCommitMessage)
|
||||||
} else {
|
} else {
|
||||||
fullMessage = fmt.Sprintf("%s: %s", commitType, message)
|
fullMessage = fmt.Sprintf("%s: %s", commitType, taskCommitMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current directory
|
// Get current directory
|
||||||
|
|
@ -107,7 +104,7 @@ func addTaskCommitCommand(parent *clir.Command) {
|
||||||
fmt.Printf("%s Committed: %s\n", successStyle.Render(">>"), fullMessage)
|
fmt.Printf("%s Committed: %s\n", successStyle.Render(">>"), fullMessage)
|
||||||
|
|
||||||
// Push if requested
|
// Push if requested
|
||||||
if push {
|
if taskCommitPush {
|
||||||
fmt.Printf("%s Pushing changes...\n", dimStyle.Render(">>"))
|
fmt.Printf("%s Pushing changes...\n", dimStyle.Render(">>"))
|
||||||
if err := agentic.PushChanges(ctx, cwd); err != nil {
|
if err := agentic.PushChanges(ctx, cwd); err != nil {
|
||||||
return fmt.Errorf("failed to push: %w", err)
|
return fmt.Errorf("failed to push: %w", err)
|
||||||
|
|
@ -116,43 +113,24 @@ func addTaskCommitCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskPRCommand(parent *clir.Command) {
|
var taskPRCmd = &cobra.Command{
|
||||||
var title string
|
Use: "task:pr [task-id]",
|
||||||
var draft bool
|
Short: "Create a pull request for a task",
|
||||||
var labels string
|
Long: `Creates a GitHub pull request linked to a task.
|
||||||
var base string
|
|
||||||
|
|
||||||
cmd := parent.NewSubCommand("task:pr", "Create a pull request for a task")
|
Requires the GitHub CLI (gh) to be installed and authenticated.
|
||||||
cmd.LongDescription("Creates a GitHub pull request linked to a task.\n\n" +
|
|
||||||
"Requires the GitHub CLI (gh) to be installed and authenticated.\n\n" +
|
|
||||||
"Examples:\n" +
|
|
||||||
" core ai task:pr abc123\n" +
|
|
||||||
" core ai task:pr abc123 --title 'Add authentication feature'\n" +
|
|
||||||
" core ai task:pr abc123 --draft --labels 'enhancement,needs-review'\n" +
|
|
||||||
" core ai task:pr abc123 --base develop")
|
|
||||||
|
|
||||||
cmd.StringFlag("title", "PR title (defaults to task title)", &title)
|
Examples:
|
||||||
cmd.BoolFlag("draft", "Create as draft PR", &draft)
|
core ai task:pr abc123
|
||||||
cmd.StringFlag("labels", "Labels to add (comma-separated)", &labels)
|
core ai task:pr abc123 --title 'Add authentication feature'
|
||||||
cmd.StringFlag("base", "Base branch (defaults to main)", &base)
|
core ai task:pr abc123 --draft --labels 'enhancement,needs-review'
|
||||||
|
core ai task:pr abc123 --base develop`,
|
||||||
cmd.Action(func() error {
|
Args: cobra.ExactArgs(1),
|
||||||
// Find task ID from args
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
args := os.Args
|
taskID := args[0]
|
||||||
var taskID string
|
|
||||||
for i, arg := range args {
|
|
||||||
if arg == "task:pr" && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
|
|
||||||
taskID = args[i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if taskID == "" {
|
|
||||||
return fmt.Errorf("task ID required")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -197,13 +175,13 @@ func addTaskPRCommand(parent *clir.Command) {
|
||||||
|
|
||||||
// Build PR options
|
// Build PR options
|
||||||
opts := agentic.PROptions{
|
opts := agentic.PROptions{
|
||||||
Title: title,
|
Title: taskPRTitle,
|
||||||
Draft: draft,
|
Draft: taskPRDraft,
|
||||||
Base: base,
|
Base: taskPRBase,
|
||||||
}
|
}
|
||||||
|
|
||||||
if labels != "" {
|
if taskPRLabels != "" {
|
||||||
opts.Labels = strings.Split(labels, ",")
|
opts.Labels = strings.Split(taskPRLabels, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create PR
|
// Create PR
|
||||||
|
|
@ -217,7 +195,28 @@ func addTaskPRCommand(parent *clir.Command) {
|
||||||
fmt.Printf(" URL: %s\n", prURL)
|
fmt.Printf(" URL: %s\n", prURL)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// task:commit command flags
|
||||||
|
taskCommitCmd.Flags().StringVarP(&taskCommitMessage, "message", "m", "", "Commit message (without task reference)")
|
||||||
|
taskCommitCmd.Flags().StringVar(&taskCommitScope, "scope", "", "Scope for the commit type (e.g., auth, api, ui)")
|
||||||
|
taskCommitCmd.Flags().BoolVar(&taskCommitPush, "push", false, "Push changes after committing")
|
||||||
|
|
||||||
|
// task:pr command flags
|
||||||
|
taskPRCmd.Flags().StringVar(&taskPRTitle, "title", "", "PR title (defaults to task title)")
|
||||||
|
taskPRCmd.Flags().BoolVar(&taskPRDraft, "draft", false, "Create as draft PR")
|
||||||
|
taskPRCmd.Flags().StringVar(&taskPRLabels, "labels", "", "Labels to add (comma-separated)")
|
||||||
|
taskPRCmd.Flags().StringVar(&taskPRBase, "base", "", "Base branch (defaults to main)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTaskCommitCommand(parent *cobra.Command) {
|
||||||
|
parent.AddCommand(taskCommitCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTaskPRCommand(parent *cobra.Command) {
|
||||||
|
parent.AddCommand(taskPRCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// inferCommitType infers the commit type from task labels.
|
// inferCommitType infers the commit type from task labels.
|
||||||
|
|
|
||||||
|
|
@ -11,34 +11,41 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/agentic"
|
"github.com/host-uk/core/pkg/agentic"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addTasksCommand(parent *clir.Command) {
|
// tasks command flags
|
||||||
var status string
|
var (
|
||||||
var priority string
|
tasksStatus string
|
||||||
var labels string
|
tasksPriority string
|
||||||
var limit int
|
tasksLabels string
|
||||||
var project string
|
tasksLimit int
|
||||||
|
tasksProject string
|
||||||
|
)
|
||||||
|
|
||||||
cmd := parent.NewSubCommand("tasks", "List available tasks from core-agentic")
|
// task command flags
|
||||||
cmd.LongDescription("Lists tasks from the core-agentic service.\n\n" +
|
var (
|
||||||
"Configuration is loaded from:\n" +
|
taskAutoSelect bool
|
||||||
" 1. Environment variables (AGENTIC_TOKEN, AGENTIC_BASE_URL)\n" +
|
taskClaim bool
|
||||||
" 2. .env file in current directory\n" +
|
taskShowContext bool
|
||||||
" 3. ~/.core/agentic.yaml\n\n" +
|
)
|
||||||
"Examples:\n" +
|
|
||||||
" core ai tasks\n" +
|
|
||||||
" core ai tasks --status pending --priority high\n" +
|
|
||||||
" core ai tasks --labels bug,urgent")
|
|
||||||
|
|
||||||
cmd.StringFlag("status", "Filter by status (pending, in_progress, completed, blocked)", &status)
|
var tasksCmd = &cobra.Command{
|
||||||
cmd.StringFlag("priority", "Filter by priority (critical, high, medium, low)", &priority)
|
Use: "tasks",
|
||||||
cmd.StringFlag("labels", "Filter by labels (comma-separated)", &labels)
|
Short: "List available tasks from core-agentic",
|
||||||
cmd.IntFlag("limit", "Max number of tasks to return (default 20)", &limit)
|
Long: `Lists tasks from the core-agentic service.
|
||||||
cmd.StringFlag("project", "Filter by project", &project)
|
|
||||||
|
|
||||||
cmd.Action(func() error {
|
Configuration is loaded from:
|
||||||
|
1. Environment variables (AGENTIC_TOKEN, AGENTIC_BASE_URL)
|
||||||
|
2. .env file in current directory
|
||||||
|
3. ~/.core/agentic.yaml
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
core ai tasks
|
||||||
|
core ai tasks --status pending --priority high
|
||||||
|
core ai tasks --labels bug,urgent`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
limit := tasksLimit
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
limit = 20
|
limit = 20
|
||||||
}
|
}
|
||||||
|
|
@ -52,17 +59,17 @@ func addTasksCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := agentic.ListOptions{
|
opts := agentic.ListOptions{
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Project: project,
|
Project: tasksProject,
|
||||||
}
|
}
|
||||||
|
|
||||||
if status != "" {
|
if tasksStatus != "" {
|
||||||
opts.Status = agentic.TaskStatus(status)
|
opts.Status = agentic.TaskStatus(tasksStatus)
|
||||||
}
|
}
|
||||||
if priority != "" {
|
if tasksPriority != "" {
|
||||||
opts.Priority = agentic.TaskPriority(priority)
|
opts.Priority = agentic.TaskPriority(tasksPriority)
|
||||||
}
|
}
|
||||||
if labels != "" {
|
if tasksLabels != "" {
|
||||||
opts.Labels = strings.Split(labels, ",")
|
opts.Labels = strings.Split(tasksLabels, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
|
@ -80,27 +87,20 @@ func addTasksCommand(parent *clir.Command) {
|
||||||
|
|
||||||
printTaskList(tasks)
|
printTaskList(tasks)
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskCommand(parent *clir.Command) {
|
var taskCmd = &cobra.Command{
|
||||||
var autoSelect bool
|
Use: "task [task-id]",
|
||||||
var claim bool
|
Short: "Show task details or auto-select a task",
|
||||||
var showContext bool
|
Long: `Shows details of a specific task or auto-selects the highest priority task.
|
||||||
|
|
||||||
cmd := parent.NewSubCommand("task", "Show task details or auto-select a task")
|
Examples:
|
||||||
cmd.LongDescription("Shows details of a specific task or auto-selects the highest priority task.\n\n" +
|
core ai task abc123 # Show task details
|
||||||
"Examples:\n" +
|
core ai task abc123 --claim # Show and claim the task
|
||||||
" core ai task abc123 # Show task details\n" +
|
core ai task abc123 --context # Show task with gathered context
|
||||||
" core ai task abc123 --claim # Show and claim the task\n" +
|
core ai task --auto # Auto-select highest priority pending task`,
|
||||||
" core ai task abc123 --context # Show task with gathered context\n" +
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
" core ai task --auto # Auto-select highest priority pending task")
|
|
||||||
|
|
||||||
cmd.BoolFlag("auto", "Auto-select highest priority pending task", &autoSelect)
|
|
||||||
cmd.BoolFlag("claim", "Claim the task after showing details", &claim)
|
|
||||||
cmd.BoolFlag("context", "Show gathered context for AI collaboration", &showContext)
|
|
||||||
|
|
||||||
cmd.Action(func() error {
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to load config: %w", err)
|
return fmt.Errorf("failed to load config: %w", err)
|
||||||
|
|
@ -113,19 +113,13 @@ func addTaskCommand(parent *clir.Command) {
|
||||||
|
|
||||||
var task *agentic.Task
|
var task *agentic.Task
|
||||||
|
|
||||||
// Get the task ID from remaining args
|
// Get the task ID from args
|
||||||
args := os.Args
|
|
||||||
var taskID string
|
var taskID string
|
||||||
|
if len(args) > 0 {
|
||||||
// Find the task ID in args (after "task" subcommand)
|
taskID = args[0]
|
||||||
for i, arg := range args {
|
|
||||||
if arg == "task" && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
|
|
||||||
taskID = args[i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if autoSelect {
|
if taskAutoSelect {
|
||||||
// Auto-select: find highest priority pending task
|
// Auto-select: find highest priority pending task
|
||||||
tasks, err := client.ListTasks(ctx, agentic.ListOptions{
|
tasks, err := client.ListTasks(ctx, agentic.ListOptions{
|
||||||
Status: agentic.StatusPending,
|
Status: agentic.StatusPending,
|
||||||
|
|
@ -153,7 +147,7 @@ func addTaskCommand(parent *clir.Command) {
|
||||||
})
|
})
|
||||||
|
|
||||||
task = &tasks[0]
|
task = &tasks[0]
|
||||||
claim = true // Auto-select implies claiming
|
taskClaim = true // Auto-select implies claiming
|
||||||
} else {
|
} else {
|
||||||
if taskID == "" {
|
if taskID == "" {
|
||||||
return fmt.Errorf("task ID required (or use --auto)")
|
return fmt.Errorf("task ID required (or use --auto)")
|
||||||
|
|
@ -166,7 +160,7 @@ func addTaskCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show context if requested
|
// Show context if requested
|
||||||
if showContext {
|
if taskShowContext {
|
||||||
cwd, _ := os.Getwd()
|
cwd, _ := os.Getwd()
|
||||||
taskCtx, err := agentic.BuildTaskContext(task, cwd)
|
taskCtx, err := agentic.BuildTaskContext(task, cwd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -178,7 +172,7 @@ func addTaskCommand(parent *clir.Command) {
|
||||||
printTaskDetails(task)
|
printTaskDetails(task)
|
||||||
}
|
}
|
||||||
|
|
||||||
if claim && task.Status == agentic.StatusPending {
|
if taskClaim && task.Status == agentic.StatusPending {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Printf("%s Claiming task...\n", dimStyle.Render(">>"))
|
fmt.Printf("%s Claiming task...\n", dimStyle.Render(">>"))
|
||||||
|
|
||||||
|
|
@ -192,7 +186,29 @@ func addTaskCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// tasks command flags
|
||||||
|
tasksCmd.Flags().StringVar(&tasksStatus, "status", "", "Filter by status (pending, in_progress, completed, blocked)")
|
||||||
|
tasksCmd.Flags().StringVar(&tasksPriority, "priority", "", "Filter by priority (critical, high, medium, low)")
|
||||||
|
tasksCmd.Flags().StringVar(&tasksLabels, "labels", "", "Filter by labels (comma-separated)")
|
||||||
|
tasksCmd.Flags().IntVar(&tasksLimit, "limit", 20, "Max number of tasks to return")
|
||||||
|
tasksCmd.Flags().StringVar(&tasksProject, "project", "", "Filter by project")
|
||||||
|
|
||||||
|
// task command flags
|
||||||
|
taskCmd.Flags().BoolVar(&taskAutoSelect, "auto", false, "Auto-select highest priority pending task")
|
||||||
|
taskCmd.Flags().BoolVar(&taskClaim, "claim", false, "Claim the task after showing details")
|
||||||
|
taskCmd.Flags().BoolVar(&taskShowContext, "context", false, "Show gathered context for AI collaboration")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTasksCommand(parent *cobra.Command) {
|
||||||
|
parent.AddCommand(tasksCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTaskCommand(parent *cobra.Command) {
|
||||||
|
parent.AddCommand(taskCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printTaskList(tasks []agentic.Task) {
|
func printTaskList(tasks []agentic.Task) {
|
||||||
|
|
|
||||||
|
|
@ -5,45 +5,39 @@ package ai
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/agentic"
|
"github.com/host-uk/core/pkg/agentic"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addTaskUpdateCommand(parent *clir.Command) {
|
// task:update command flags
|
||||||
var status string
|
var (
|
||||||
var progress int
|
taskUpdateStatus string
|
||||||
var notes string
|
taskUpdateProgress int
|
||||||
|
taskUpdateNotes string
|
||||||
|
)
|
||||||
|
|
||||||
cmd := parent.NewSubCommand("task:update", "Update task status or progress")
|
// task:complete command flags
|
||||||
cmd.LongDescription("Updates a task's status, progress, or adds notes.\n\n" +
|
var (
|
||||||
"Examples:\n" +
|
taskCompleteOutput string
|
||||||
" core ai task:update abc123 --status in_progress\n" +
|
taskCompleteFailed bool
|
||||||
" core ai task:update abc123 --progress 50 --notes 'Halfway done'")
|
taskCompleteErrorMsg string
|
||||||
|
)
|
||||||
|
|
||||||
cmd.StringFlag("status", "New status (pending, in_progress, completed, blocked)", &status)
|
var taskUpdateCmd = &cobra.Command{
|
||||||
cmd.IntFlag("progress", "Progress percentage (0-100)", &progress)
|
Use: "task:update [task-id]",
|
||||||
cmd.StringFlag("notes", "Notes about the update", ¬es)
|
Short: "Update task status or progress",
|
||||||
|
Long: `Updates a task's status, progress, or adds notes.
|
||||||
|
|
||||||
cmd.Action(func() error {
|
Examples:
|
||||||
// Find task ID from args
|
core ai task:update abc123 --status in_progress
|
||||||
args := os.Args
|
core ai task:update abc123 --progress 50 --notes 'Halfway done'`,
|
||||||
var taskID string
|
Args: cobra.ExactArgs(1),
|
||||||
for i, arg := range args {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if arg == "task:update" && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
|
taskID := args[0]
|
||||||
taskID = args[i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if taskID == "" {
|
if taskUpdateStatus == "" && taskUpdateProgress == 0 && taskUpdateNotes == "" {
|
||||||
return fmt.Errorf("task ID required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if status == "" && progress == 0 && notes == "" {
|
|
||||||
return fmt.Errorf("at least one of --status, --progress, or --notes required")
|
return fmt.Errorf("at least one of --status, --progress, or --notes required")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,11 +52,11 @@ func addTaskUpdateCommand(parent *clir.Command) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
update := agentic.TaskUpdate{
|
update := agentic.TaskUpdate{
|
||||||
Progress: progress,
|
Progress: taskUpdateProgress,
|
||||||
Notes: notes,
|
Notes: taskUpdateNotes,
|
||||||
}
|
}
|
||||||
if status != "" {
|
if taskUpdateStatus != "" {
|
||||||
update.Status = agentic.TaskStatus(status)
|
update.Status = agentic.TaskStatus(taskUpdateStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.UpdateTask(ctx, taskID, update); err != nil {
|
if err := client.UpdateTask(ctx, taskID, update); err != nil {
|
||||||
|
|
@ -71,38 +65,20 @@ func addTaskUpdateCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("%s Task %s updated successfully\n", successStyle.Render(">>"), taskID)
|
fmt.Printf("%s Task %s updated successfully\n", successStyle.Render(">>"), taskID)
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTaskCompleteCommand(parent *clir.Command) {
|
var taskCompleteCmd = &cobra.Command{
|
||||||
var output string
|
Use: "task:complete [task-id]",
|
||||||
var failed bool
|
Short: "Mark a task as completed",
|
||||||
var errorMsg string
|
Long: `Marks a task as completed with optional output and artifacts.
|
||||||
|
|
||||||
cmd := parent.NewSubCommand("task:complete", "Mark a task as completed")
|
Examples:
|
||||||
cmd.LongDescription("Marks a task as completed with optional output and artifacts.\n\n" +
|
core ai task:complete abc123 --output 'Feature implemented'
|
||||||
"Examples:\n" +
|
core ai task:complete abc123 --failed --error 'Build failed'`,
|
||||||
" core ai task:complete abc123 --output 'Feature implemented'\n" +
|
Args: cobra.ExactArgs(1),
|
||||||
" core ai task:complete abc123 --failed --error 'Build failed'")
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
taskID := args[0]
|
||||||
cmd.StringFlag("output", "Summary of the completed work", &output)
|
|
||||||
cmd.BoolFlag("failed", "Mark the task as failed", &failed)
|
|
||||||
cmd.StringFlag("error", "Error message if failed", &errorMsg)
|
|
||||||
|
|
||||||
cmd.Action(func() error {
|
|
||||||
// Find task ID from args
|
|
||||||
args := os.Args
|
|
||||||
var taskID string
|
|
||||||
for i, arg := range args {
|
|
||||||
if arg == "task:complete" && i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
|
|
||||||
taskID = args[i+1]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if taskID == "" {
|
|
||||||
return fmt.Errorf("task ID required")
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg, err := agentic.LoadConfig("")
|
cfg, err := agentic.LoadConfig("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -115,20 +91,40 @@ func addTaskCompleteCommand(parent *clir.Command) {
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
result := agentic.TaskResult{
|
result := agentic.TaskResult{
|
||||||
Success: !failed,
|
Success: !taskCompleteFailed,
|
||||||
Output: output,
|
Output: taskCompleteOutput,
|
||||||
ErrorMessage: errorMsg,
|
ErrorMessage: taskCompleteErrorMsg,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.CompleteTask(ctx, taskID, result); err != nil {
|
if err := client.CompleteTask(ctx, taskID, result); err != nil {
|
||||||
return fmt.Errorf("failed to complete task: %w", err)
|
return fmt.Errorf("failed to complete task: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if failed {
|
if taskCompleteFailed {
|
||||||
fmt.Printf("%s Task %s marked as failed\n", errorStyle.Render(">>"), taskID)
|
fmt.Printf("%s Task %s marked as failed\n", errorStyle.Render(">>"), taskID)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("%s Task %s completed successfully\n", successStyle.Render(">>"), taskID)
|
fmt.Printf("%s Task %s completed successfully\n", successStyle.Render(">>"), taskID)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// task:update command flags
|
||||||
|
taskUpdateCmd.Flags().StringVar(&taskUpdateStatus, "status", "", "New status (pending, in_progress, completed, blocked)")
|
||||||
|
taskUpdateCmd.Flags().IntVar(&taskUpdateProgress, "progress", 0, "Progress percentage (0-100)")
|
||||||
|
taskUpdateCmd.Flags().StringVar(&taskUpdateNotes, "notes", "", "Notes about the update")
|
||||||
|
|
||||||
|
// task:complete command flags
|
||||||
|
taskCompleteCmd.Flags().StringVar(&taskCompleteOutput, "output", "", "Summary of the completed work")
|
||||||
|
taskCompleteCmd.Flags().BoolVar(&taskCompleteFailed, "failed", false, "Mark the task as failed")
|
||||||
|
taskCompleteCmd.Flags().StringVar(&taskCompleteErrorMsg, "error", "", "Error message if failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTaskUpdateCommand(parent *cobra.Command) {
|
||||||
|
parent.AddCommand(taskUpdateCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTaskCompleteCommand(parent *cobra.Command) {
|
||||||
|
parent.AddCommand(taskCompleteCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,52 +10,70 @@
|
||||||
// - claude: Claude Code CLI integration (planned)
|
// - claude: Claude Code CLI integration (planned)
|
||||||
package ai
|
package ai
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'ai' command and all subcommands.
|
var aiCmd = &cobra.Command{
|
||||||
func AddCommands(app *clir.Cli) {
|
Use: "ai",
|
||||||
aiCmd := app.NewSubCommand("ai", "AI agent task management")
|
Short: "AI agent task management",
|
||||||
aiCmd.LongDescription("Manage tasks from the core-agentic service for AI-assisted development.\n\n" +
|
Long: `Manage tasks from the core-agentic service for AI-assisted development.
|
||||||
"Commands:\n" +
|
|
||||||
" tasks List tasks (filterable by status, priority, labels)\n" +
|
|
||||||
" task View task details or auto-select highest priority\n" +
|
|
||||||
" task:update Update task status or progress\n" +
|
|
||||||
" task:complete Mark task as completed or failed\n" +
|
|
||||||
" task:commit Create git commit with task reference\n" +
|
|
||||||
" task:pr Create GitHub PR linked to task\n" +
|
|
||||||
" claude Claude Code integration\n\n" +
|
|
||||||
"Workflow:\n" +
|
|
||||||
" core ai tasks # List pending tasks\n" +
|
|
||||||
" core ai task --auto --claim # Auto-select and claim a task\n" +
|
|
||||||
" core ai task:commit <id> -m 'msg' # Commit with task reference\n" +
|
|
||||||
" core ai task:complete <id> # Mark task done")
|
|
||||||
|
|
||||||
// Add Claude command
|
Commands:
|
||||||
addClaudeCommand(aiCmd)
|
tasks List tasks (filterable by status, priority, labels)
|
||||||
|
task View task details or auto-select highest priority
|
||||||
|
task:update Update task status or progress
|
||||||
|
task:complete Mark task as completed or failed
|
||||||
|
task:commit Create git commit with task reference
|
||||||
|
task:pr Create GitHub PR linked to task
|
||||||
|
claude Claude Code integration
|
||||||
|
|
||||||
|
Workflow:
|
||||||
|
core ai tasks # List pending tasks
|
||||||
|
core ai task --auto --claim # Auto-select and claim a task
|
||||||
|
core ai task:commit <id> -m 'msg' # Commit with task reference
|
||||||
|
core ai task:complete <id> # Mark task done`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var claudeCmd = &cobra.Command{
|
||||||
|
Use: "claude",
|
||||||
|
Short: "Claude Code integration",
|
||||||
|
Long: `Tools for working with Claude Code.
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
run Run Claude in the current directory
|
||||||
|
config Manage Claude configuration`,
|
||||||
|
}
|
||||||
|
|
||||||
|
var claudeRunCmd = &cobra.Command{
|
||||||
|
Use: "run",
|
||||||
|
Short: "Run Claude Code in the current directory",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runClaudeCode()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var claudeConfigCmd = &cobra.Command{
|
||||||
|
Use: "config",
|
||||||
|
Short: "Manage Claude configuration",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return showClaudeConfig()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Add Claude subcommands
|
||||||
|
claudeCmd.AddCommand(claudeRunCmd)
|
||||||
|
claudeCmd.AddCommand(claudeConfigCmd)
|
||||||
|
|
||||||
|
// Add Claude command to ai
|
||||||
|
aiCmd.AddCommand(claudeCmd)
|
||||||
|
|
||||||
// Add agentic task commands
|
// Add agentic task commands
|
||||||
AddAgenticCommands(aiCmd)
|
AddAgenticCommands(aiCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addClaudeCommand adds the 'claude' subcommand for Claude Code integration.
|
// AddCommands registers the 'ai' command and all subcommands.
|
||||||
func addClaudeCommand(parent *clir.Command) {
|
func AddCommands(root *cobra.Command) {
|
||||||
claudeCmd := parent.NewSubCommand("claude", "Claude Code integration")
|
root.AddCommand(aiCmd)
|
||||||
claudeCmd.LongDescription("Tools for working with Claude Code.\n\n" +
|
|
||||||
"Commands:\n" +
|
|
||||||
" run Run Claude in the current directory\n" +
|
|
||||||
" config Manage Claude configuration")
|
|
||||||
|
|
||||||
// core ai claude run
|
|
||||||
runCmd := claudeCmd.NewSubCommand("run", "Run Claude Code in the current directory")
|
|
||||||
runCmd.Action(func() error {
|
|
||||||
return runClaudeCode()
|
|
||||||
})
|
|
||||||
|
|
||||||
// core ai claude config
|
|
||||||
configCmd := claudeCmd.NewSubCommand("config", "Manage Claude configuration")
|
|
||||||
configCmd.Action(func() error {
|
|
||||||
return showClaudeConfig()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runClaudeCode() error {
|
func runClaudeCode() error {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Build command styles
|
// Build command styles
|
||||||
|
|
@ -32,100 +32,130 @@ var (
|
||||||
//go:embed all:tmpl/gui
|
//go:embed all:tmpl/gui
|
||||||
var guiTemplate embed.FS
|
var guiTemplate embed.FS
|
||||||
|
|
||||||
// AddBuildCommand adds the new build command and its subcommands to the clir app.
|
|
||||||
func AddBuildCommand(app *clir.Cli) {
|
|
||||||
buildCmd := app.NewSubCommand("build", "Build projects with auto-detection and cross-compilation")
|
|
||||||
buildCmd.LongDescription("Builds the current project with automatic type detection.\n" +
|
|
||||||
"Supports Go, Wails, Docker, LinuxKit, and Taskfile projects.\n" +
|
|
||||||
"Configuration can be provided via .core/build.yaml or command-line flags.\n\n" +
|
|
||||||
"Examples:\n" +
|
|
||||||
" core build # Auto-detect and build\n" +
|
|
||||||
" core build --type docker # Build Docker image\n" +
|
|
||||||
" core build --type linuxkit # Build LinuxKit image\n" +
|
|
||||||
" core build --type linuxkit --config linuxkit.yml --format qcow2-bios")
|
|
||||||
|
|
||||||
// Flags for the main build command
|
// Flags for the main build command
|
||||||
var buildType string
|
var (
|
||||||
var ciMode bool
|
buildType string
|
||||||
var targets string
|
ciMode bool
|
||||||
var outputDir string
|
targets string
|
||||||
var doArchive bool
|
outputDir string
|
||||||
var doChecksum bool
|
doArchive bool
|
||||||
|
doChecksum bool
|
||||||
|
|
||||||
// Docker/LinuxKit specific flags
|
// Docker/LinuxKit specific flags
|
||||||
var configPath string
|
configPath string
|
||||||
var format string
|
format string
|
||||||
var push bool
|
push bool
|
||||||
var imageName string
|
imageName string
|
||||||
|
|
||||||
// Signing flags
|
// Signing flags
|
||||||
var noSign bool
|
noSign bool
|
||||||
var notarize bool
|
notarize bool
|
||||||
|
|
||||||
buildCmd.StringFlag("type", "Builder type (go, wails, docker, linuxkit, taskfile) - auto-detected if not specified", &buildType)
|
// from-path subcommand
|
||||||
buildCmd.BoolFlag("ci", "CI mode - minimal output with JSON artifact list at the end", &ciMode)
|
fromPath string
|
||||||
buildCmd.StringFlag("targets", "Comma-separated OS/arch pairs (e.g., linux/amd64,darwin/arm64)", &targets)
|
|
||||||
buildCmd.StringFlag("output", "Output directory for artifacts (default: dist)", &outputDir)
|
|
||||||
buildCmd.BoolFlag("archive", "Create archives (tar.gz for linux/darwin, zip for windows) - default: true", &doArchive)
|
|
||||||
buildCmd.BoolFlag("checksum", "Generate SHA256 checksums and CHECKSUMS.txt - default: true", &doChecksum)
|
|
||||||
|
|
||||||
// Docker/LinuxKit specific
|
// pwa subcommand
|
||||||
buildCmd.StringFlag("config", "Config file path (for linuxkit: YAML config, for docker: Dockerfile)", &configPath)
|
pwaURL string
|
||||||
buildCmd.StringFlag("format", "Output format for linuxkit (iso-bios, qcow2-bios, raw, vmdk)", &format)
|
|
||||||
buildCmd.BoolFlag("push", "Push Docker image after build (default: false)", &push)
|
|
||||||
buildCmd.StringFlag("image", "Docker image name (e.g., host-uk/core-devops)", &imageName)
|
|
||||||
|
|
||||||
// Signing flags
|
// sdk subcommand
|
||||||
buildCmd.BoolFlag("no-sign", "Skip all code signing", &noSign)
|
sdkSpec string
|
||||||
buildCmd.BoolFlag("notarize", "Enable macOS notarization (requires Apple credentials)", ¬arize)
|
sdkLang string
|
||||||
|
sdkVersion string
|
||||||
|
sdkDryRun bool
|
||||||
|
)
|
||||||
|
|
||||||
// Set defaults for archive and checksum (true by default)
|
var buildCmd = &cobra.Command{
|
||||||
doArchive = true
|
Use: "build",
|
||||||
doChecksum = true
|
Short: "Build projects with auto-detection and cross-compilation",
|
||||||
|
Long: `Builds the current project with automatic type detection.
|
||||||
|
Supports Go, Wails, Docker, LinuxKit, and Taskfile projects.
|
||||||
|
Configuration can be provided via .core/build.yaml or command-line flags.
|
||||||
|
|
||||||
// Default action for `core build` (no subcommand)
|
Examples:
|
||||||
buildCmd.Action(func() error {
|
core build # Auto-detect and build
|
||||||
|
core build --type docker # Build Docker image
|
||||||
|
core build --type linuxkit # Build LinuxKit image
|
||||||
|
core build --type linuxkit --config linuxkit.yml --format qcow2-bios`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runProjectBuild(buildType, ciMode, targets, outputDir, doArchive, doChecksum, configPath, format, push, imageName, noSign, notarize)
|
return runProjectBuild(buildType, ciMode, targets, outputDir, doArchive, doChecksum, configPath, format, push, imageName, noSign, notarize)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// --- `build from-path` command (legacy PWA/GUI build) ---
|
var fromPathCmd = &cobra.Command{
|
||||||
fromPathCmd := buildCmd.NewSubCommand("from-path", "Build from a local directory.")
|
Use: "from-path",
|
||||||
var fromPath string
|
Short: "Build from a local directory.",
|
||||||
fromPathCmd.StringFlag("path", "The path to the static web application files.", &fromPath)
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
fromPathCmd.Action(func() error {
|
|
||||||
if fromPath == "" {
|
if fromPath == "" {
|
||||||
return errPathRequired
|
return errPathRequired
|
||||||
}
|
}
|
||||||
return runBuild(fromPath)
|
return runBuild(fromPath)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// --- `build pwa` command (legacy PWA build) ---
|
var pwaCmd = &cobra.Command{
|
||||||
pwaCmd := buildCmd.NewSubCommand("pwa", "Build from a live PWA URL.")
|
Use: "pwa",
|
||||||
var pwaURL string
|
Short: "Build from a live PWA URL.",
|
||||||
pwaCmd.StringFlag("url", "The URL of the PWA to build.", &pwaURL)
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
pwaCmd.Action(func() error {
|
|
||||||
if pwaURL == "" {
|
if pwaURL == "" {
|
||||||
return errURLRequired
|
return errURLRequired
|
||||||
}
|
}
|
||||||
return runPwaBuild(pwaURL)
|
return runPwaBuild(pwaURL)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
// --- `build sdk` command ---
|
|
||||||
sdkBuildCmd := buildCmd.NewSubCommand("sdk", "Generate API SDKs from OpenAPI spec")
|
var sdkBuildCmd = &cobra.Command{
|
||||||
sdkBuildCmd.LongDescription("Generates typed API clients from OpenAPI specifications.\n" +
|
Use: "sdk",
|
||||||
"Supports TypeScript, Python, Go, and PHP.\n\n" +
|
Short: "Generate API SDKs from OpenAPI spec",
|
||||||
"Examples:\n" +
|
Long: `Generates typed API clients from OpenAPI specifications.
|
||||||
" core build sdk # Generate all configured SDKs\n" +
|
Supports TypeScript, Python, Go, and PHP.
|
||||||
" core build sdk --lang typescript # Generate only TypeScript SDK\n" +
|
|
||||||
" core build sdk --spec api.yaml # Use specific OpenAPI spec")
|
Examples:
|
||||||
|
core build sdk # Generate all configured SDKs
|
||||||
var sdkSpec, sdkLang, sdkVersion string
|
core build sdk --lang typescript # Generate only TypeScript SDK
|
||||||
var sdkDryRun bool
|
core build sdk --spec api.yaml # Use specific OpenAPI spec`,
|
||||||
sdkBuildCmd.StringFlag("spec", "Path to OpenAPI spec file", &sdkSpec)
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
sdkBuildCmd.StringFlag("lang", "Generate only this language (typescript, python, go, php)", &sdkLang)
|
return runBuildSDK(sdkSpec, sdkLang, sdkVersion, sdkDryRun)
|
||||||
sdkBuildCmd.StringFlag("version", "Version to embed in generated SDKs", &sdkVersion)
|
},
|
||||||
sdkBuildCmd.BoolFlag("dry-run", "Show what would be generated without writing files", &sdkDryRun)
|
}
|
||||||
sdkBuildCmd.Action(func() error {
|
|
||||||
return runBuildSDK(sdkSpec, sdkLang, sdkVersion, sdkDryRun)
|
func init() {
|
||||||
})
|
// Main build command flags
|
||||||
|
buildCmd.Flags().StringVar(&buildType, "type", "", "Builder type (go, wails, docker, linuxkit, taskfile) - auto-detected if not specified")
|
||||||
|
buildCmd.Flags().BoolVar(&ciMode, "ci", false, "CI mode - minimal output with JSON artifact list at the end")
|
||||||
|
buildCmd.Flags().StringVar(&targets, "targets", "", "Comma-separated OS/arch pairs (e.g., linux/amd64,darwin/arm64)")
|
||||||
|
buildCmd.Flags().StringVar(&outputDir, "output", "", "Output directory for artifacts (default: dist)")
|
||||||
|
buildCmd.Flags().BoolVar(&doArchive, "archive", true, "Create archives (tar.gz for linux/darwin, zip for windows)")
|
||||||
|
buildCmd.Flags().BoolVar(&doChecksum, "checksum", true, "Generate SHA256 checksums and CHECKSUMS.txt")
|
||||||
|
|
||||||
|
// Docker/LinuxKit specific
|
||||||
|
buildCmd.Flags().StringVar(&configPath, "config", "", "Config file path (for linuxkit: YAML config, for docker: Dockerfile)")
|
||||||
|
buildCmd.Flags().StringVar(&format, "format", "", "Output format for linuxkit (iso-bios, qcow2-bios, raw, vmdk)")
|
||||||
|
buildCmd.Flags().BoolVar(&push, "push", false, "Push Docker image after build")
|
||||||
|
buildCmd.Flags().StringVar(&imageName, "image", "", "Docker image name (e.g., host-uk/core-devops)")
|
||||||
|
|
||||||
|
// Signing flags
|
||||||
|
buildCmd.Flags().BoolVar(&noSign, "no-sign", false, "Skip all code signing")
|
||||||
|
buildCmd.Flags().BoolVar(¬arize, "notarize", false, "Enable macOS notarization (requires Apple credentials)")
|
||||||
|
|
||||||
|
// from-path subcommand flags
|
||||||
|
fromPathCmd.Flags().StringVar(&fromPath, "path", "", "The path to the static web application files.")
|
||||||
|
|
||||||
|
// pwa subcommand flags
|
||||||
|
pwaCmd.Flags().StringVar(&pwaURL, "url", "", "The URL of the PWA to build.")
|
||||||
|
|
||||||
|
// sdk subcommand flags
|
||||||
|
sdkBuildCmd.Flags().StringVar(&sdkSpec, "spec", "", "Path to OpenAPI spec file")
|
||||||
|
sdkBuildCmd.Flags().StringVar(&sdkLang, "lang", "", "Generate only this language (typescript, python, go, php)")
|
||||||
|
sdkBuildCmd.Flags().StringVar(&sdkVersion, "version", "", "Version to embed in generated SDKs")
|
||||||
|
sdkBuildCmd.Flags().BoolVar(&sdkDryRun, "dry-run", false, "Show what would be generated without writing files")
|
||||||
|
|
||||||
|
// Add subcommands
|
||||||
|
buildCmd.AddCommand(fromPathCmd)
|
||||||
|
buildCmd.AddCommand(pwaCmd)
|
||||||
|
buildCmd.AddCommand(sdkBuildCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddBuildCommand adds the new build command and its subcommands to the cobra app.
|
||||||
|
func AddBuildCommand(root *cobra.Command) {
|
||||||
|
root.AddCommand(buildCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,9 @@
|
||||||
// - build sdk: Generate API SDKs from OpenAPI spec
|
// - build sdk: Generate API SDKs from OpenAPI spec
|
||||||
package build
|
package build
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'build' command and all subcommands.
|
// AddCommands registers the 'build' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddBuildCommand(app)
|
AddBuildCommand(root)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package ci
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared
|
// Style aliases from shared
|
||||||
|
|
@ -15,52 +15,75 @@ var (
|
||||||
releaseValueStyle = shared.ValueStyle
|
releaseValueStyle = shared.ValueStyle
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddCIReleaseCommand adds the release command and its subcommands.
|
// Flag variables for ci command
|
||||||
func AddCIReleaseCommand(app *clir.Cli) {
|
var (
|
||||||
releaseCmd := app.NewSubCommand("ci", "Publish releases (dry-run by default)")
|
ciGoForLaunch bool
|
||||||
releaseCmd.LongDescription("Publishes pre-built artifacts from dist/ to configured targets.\n" +
|
ciVersion string
|
||||||
"Run 'core build' first to create artifacts.\n\n" +
|
ciDraft bool
|
||||||
"SAFE BY DEFAULT: Runs in dry-run mode unless --we-are-go-for-launch is specified.\n\n" +
|
ciPrerelease bool
|
||||||
"Configuration: .core/release.yaml")
|
)
|
||||||
|
|
||||||
// Flags for the main release command
|
// Flag variables for changelog subcommand
|
||||||
var goForLaunch bool
|
var (
|
||||||
var version string
|
changelogFromRef string
|
||||||
var draft bool
|
changelogToRef string
|
||||||
var prerelease bool
|
)
|
||||||
|
|
||||||
releaseCmd.BoolFlag("we-are-go-for-launch", "Actually publish (default is dry-run for safety)", &goForLaunch)
|
var ciCmd = &cobra.Command{
|
||||||
releaseCmd.StringFlag("version", "Version to release (e.g., v1.2.3)", &version)
|
Use: "ci",
|
||||||
releaseCmd.BoolFlag("draft", "Create release as a draft", &draft)
|
Short: "Publish releases (dry-run by default)",
|
||||||
releaseCmd.BoolFlag("prerelease", "Mark release as a prerelease", &prerelease)
|
Long: `Publishes pre-built artifacts from dist/ to configured targets.
|
||||||
|
Run 'core build' first to create artifacts.
|
||||||
|
|
||||||
// Default action for `core ci` - dry-run by default for safety
|
SAFE BY DEFAULT: Runs in dry-run mode unless --we-are-go-for-launch is specified.
|
||||||
releaseCmd.Action(func() error {
|
|
||||||
dryRun := !goForLaunch
|
|
||||||
return runCIPublish(dryRun, version, draft, prerelease)
|
|
||||||
})
|
|
||||||
|
|
||||||
// `release init` subcommand
|
Configuration: .core/release.yaml`,
|
||||||
initCmd := releaseCmd.NewSubCommand("init", "Initialize release configuration")
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
initCmd.LongDescription("Creates a .core/release.yaml configuration file interactively.")
|
dryRun := !ciGoForLaunch
|
||||||
initCmd.Action(func() error {
|
return runCIPublish(dryRun, ciVersion, ciDraft, ciPrerelease)
|
||||||
return runCIReleaseInit()
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
// `release changelog` subcommand
|
var ciInitCmd = &cobra.Command{
|
||||||
changelogCmd := releaseCmd.NewSubCommand("changelog", "Generate changelog")
|
Use: "init",
|
||||||
changelogCmd.LongDescription("Generates a changelog from conventional commits.")
|
Short: "Initialize release configuration",
|
||||||
var fromRef, toRef string
|
Long: "Creates a .core/release.yaml configuration file interactively.",
|
||||||
changelogCmd.StringFlag("from", "Starting ref (default: previous tag)", &fromRef)
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
changelogCmd.StringFlag("to", "Ending ref (default: HEAD)", &toRef)
|
return runCIReleaseInit()
|
||||||
changelogCmd.Action(func() error {
|
},
|
||||||
return runChangelog(fromRef, toRef)
|
}
|
||||||
})
|
|
||||||
|
var ciChangelogCmd = &cobra.Command{
|
||||||
// `release version` subcommand
|
Use: "changelog",
|
||||||
versionCmd := releaseCmd.NewSubCommand("version", "Show or set version")
|
Short: "Generate changelog",
|
||||||
versionCmd.LongDescription("Shows the determined version or validates a version string.")
|
Long: "Generates a changelog from conventional commits.",
|
||||||
versionCmd.Action(func() error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runCIReleaseVersion()
|
return runChangelog(changelogFromRef, changelogToRef)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var ciVersionCmd = &cobra.Command{
|
||||||
|
Use: "version",
|
||||||
|
Short: "Show or set version",
|
||||||
|
Long: "Shows the determined version or validates a version string.",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runCIReleaseVersion()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Main ci command flags
|
||||||
|
ciCmd.Flags().BoolVar(&ciGoForLaunch, "we-are-go-for-launch", false, "Actually publish (default is dry-run for safety)")
|
||||||
|
ciCmd.Flags().StringVar(&ciVersion, "version", "", "Version to release (e.g., v1.2.3)")
|
||||||
|
ciCmd.Flags().BoolVar(&ciDraft, "draft", false, "Create release as a draft")
|
||||||
|
ciCmd.Flags().BoolVar(&ciPrerelease, "prerelease", false, "Mark release as a prerelease")
|
||||||
|
|
||||||
|
// Changelog subcommand flags
|
||||||
|
ciChangelogCmd.Flags().StringVar(&changelogFromRef, "from", "", "Starting ref (default: previous tag)")
|
||||||
|
ciChangelogCmd.Flags().StringVar(&changelogToRef, "to", "", "Ending ref (default: HEAD)")
|
||||||
|
|
||||||
|
// Add subcommands
|
||||||
|
ciCmd.AddCommand(ciInitCmd)
|
||||||
|
ciCmd.AddCommand(ciChangelogCmd)
|
||||||
|
ciCmd.AddCommand(ciVersionCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@
|
||||||
// Configuration via .core/release.yaml.
|
// Configuration via .core/release.yaml.
|
||||||
package ci
|
package ci
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'ci' command and all subcommands.
|
// AddCommands registers the 'ci' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddCIReleaseCommand(app)
|
root.AddCommand(ciCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
94
cmd/core.go
94
cmd/core.go
|
|
@ -17,32 +17,92 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"os"
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
|
"github.com/host-uk/core/cmd/shared"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Terminal styles using Tailwind color palette.
|
// Terminal styles using Tailwind colour palette (from shared package).
|
||||||
var (
|
var (
|
||||||
// coreStyle is used for primary headings and the CLI name.
|
// coreStyle is used for primary headings and the CLI name.
|
||||||
coreStyle = lipgloss.NewStyle().
|
coreStyle = shared.RepoNameStyle
|
||||||
Foreground(lipgloss.Color("#3b82f6")). // blue-500
|
|
||||||
Bold(true)
|
|
||||||
|
|
||||||
// subPkgStyle is used for subcommand names and secondary headings.
|
|
||||||
subPkgStyle = lipgloss.NewStyle().
|
|
||||||
Foreground(lipgloss.Color("#e2e8f0")). // gray-200
|
|
||||||
Bold(true)
|
|
||||||
|
|
||||||
// linkStyle is used for URLs and clickable references.
|
// linkStyle is used for URLs and clickable references.
|
||||||
linkStyle = lipgloss.NewStyle().
|
linkStyle = shared.LinkStyle
|
||||||
Foreground(lipgloss.Color("#3b82f6")). // blue-500
|
|
||||||
Underline(true)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// rootCmd is the base command for the CLI.
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "core",
|
||||||
|
Short: "CLI tool for development and production",
|
||||||
|
Version: "0.1.0",
|
||||||
|
}
|
||||||
|
|
||||||
// Execute initialises and runs the CLI application.
|
// Execute initialises and runs the CLI application.
|
||||||
// Commands are registered based on build tags (see core_ci.go and core_dev.go).
|
// Commands are registered based on build tags (see core_ci.go and core_dev.go).
|
||||||
func Execute() error {
|
func Execute() error {
|
||||||
app := clir.NewCli("core", "CLI tool for development and production", "0.1.0")
|
return rootCmd.Execute()
|
||||||
registerCommands(app)
|
}
|
||||||
return app.Run()
|
|
||||||
|
func init() {
|
||||||
|
// Add shell completion command
|
||||||
|
rootCmd.AddCommand(completionCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// completionCmd generates shell completion scripts.
|
||||||
|
var completionCmd = &cobra.Command{
|
||||||
|
Use: "completion [bash|zsh|fish|powershell]",
|
||||||
|
Short: "Generate shell completion script",
|
||||||
|
Long: `Generate shell completion script for the specified shell.
|
||||||
|
|
||||||
|
To load completions:
|
||||||
|
|
||||||
|
Bash:
|
||||||
|
$ source <(core completion bash)
|
||||||
|
|
||||||
|
# To load completions for each session, execute once:
|
||||||
|
# Linux:
|
||||||
|
$ core completion bash > /etc/bash_completion.d/core
|
||||||
|
# macOS:
|
||||||
|
$ core completion bash > $(brew --prefix)/etc/bash_completion.d/core
|
||||||
|
|
||||||
|
Zsh:
|
||||||
|
# If shell completion is not already enabled in your environment,
|
||||||
|
# you will need to enable it. You can execute the following once:
|
||||||
|
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||||
|
|
||||||
|
# To load completions for each session, execute once:
|
||||||
|
$ core completion zsh > "${fpath[1]}/_core"
|
||||||
|
|
||||||
|
# You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
|
Fish:
|
||||||
|
$ core completion fish | source
|
||||||
|
|
||||||
|
# To load completions for each session, execute once:
|
||||||
|
$ core completion fish > ~/.config/fish/completions/core.fish
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
PS> core completion powershell | Out-String | Invoke-Expression
|
||||||
|
|
||||||
|
# To load completions for every new session, run:
|
||||||
|
PS> core completion powershell > core.ps1
|
||||||
|
# and source this file from your PowerShell profile.
|
||||||
|
`,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
switch args[0] {
|
||||||
|
case "bash":
|
||||||
|
_ = cmd.Root().GenBashCompletion(os.Stdout)
|
||||||
|
case "zsh":
|
||||||
|
_ = cmd.Root().GenZshCompletion(os.Stdout)
|
||||||
|
case "fish":
|
||||||
|
_ = cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||||
|
case "powershell":
|
||||||
|
_ = cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,11 @@ import (
|
||||||
"github.com/host-uk/core/cmd/ci"
|
"github.com/host-uk/core/cmd/ci"
|
||||||
"github.com/host-uk/core/cmd/doctor"
|
"github.com/host-uk/core/cmd/doctor"
|
||||||
"github.com/host-uk/core/cmd/sdk"
|
"github.com/host-uk/core/cmd/sdk"
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// registerCommands adds CI/release commands only.
|
func init() {
|
||||||
func registerCommands(app *clir.Cli) {
|
build.AddCommands(rootCmd)
|
||||||
build.AddCommands(app)
|
ci.AddCommands(rootCmd)
|
||||||
ci.AddCommands(app)
|
sdk.AddCommands(rootCmd)
|
||||||
sdk.AddCommands(app)
|
doctor.AddCommands(rootCmd)
|
||||||
doctor.AddCommands(app)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,31 +35,29 @@ import (
|
||||||
"github.com/host-uk/core/cmd/setup"
|
"github.com/host-uk/core/cmd/setup"
|
||||||
testcmd "github.com/host-uk/core/cmd/test"
|
testcmd "github.com/host-uk/core/cmd/test"
|
||||||
"github.com/host-uk/core/cmd/vm"
|
"github.com/host-uk/core/cmd/vm"
|
||||||
"github.com/leaanthony/clir"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// registerCommands adds all development commands.
|
func init() {
|
||||||
func registerCommands(app *clir.Cli) {
|
|
||||||
// Multi-repo workflow
|
// Multi-repo workflow
|
||||||
dev.AddCommands(app)
|
dev.AddCommands(rootCmd)
|
||||||
|
|
||||||
// AI agent tools
|
// AI agent tools
|
||||||
ai.AddCommands(app)
|
ai.AddCommands(rootCmd)
|
||||||
|
|
||||||
// Language tooling
|
// Language tooling
|
||||||
gocmd.AddCommands(app)
|
gocmd.AddCommands(rootCmd)
|
||||||
php.AddCommands(app)
|
php.AddCommands(rootCmd)
|
||||||
|
|
||||||
// Build and release
|
// Build and release
|
||||||
build.AddCommands(app)
|
build.AddCommands(rootCmd)
|
||||||
ci.AddCommands(app)
|
ci.AddCommands(rootCmd)
|
||||||
sdk.AddCommands(app)
|
sdk.AddCommands(rootCmd)
|
||||||
|
|
||||||
// Environment management
|
// Environment management
|
||||||
pkg.AddCommands(app)
|
pkg.AddCommands(rootCmd)
|
||||||
vm.AddCommands(app)
|
vm.AddCommands(rootCmd)
|
||||||
docs.AddCommands(app)
|
docs.AddCommands(rootCmd)
|
||||||
setup.AddCommands(app)
|
setup.AddCommands(rootCmd)
|
||||||
doctor.AddCommands(app)
|
doctor.AddCommands(rootCmd)
|
||||||
testcmd.AddCommands(app)
|
testcmd.AddCommands(rootCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ package dev
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared package
|
// Style aliases from shared package
|
||||||
|
|
@ -64,28 +64,36 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddCommands registers the 'dev' command and all subcommands.
|
// AddCommands registers the 'dev' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
devCmd := app.NewSubCommand("dev", "Multi-repo development workflow")
|
devCmd := &cobra.Command{
|
||||||
devCmd.LongDescription("Manage multiple git repositories and GitHub integration.\n\n" +
|
Use: "dev",
|
||||||
"Uses repos.yaml to discover repositories. Falls back to scanning\n" +
|
Short: "Multi-repo development workflow",
|
||||||
"the current directory if no registry is found.\n\n" +
|
Long: `Manage multiple git repositories and GitHub integration.
|
||||||
"Git Operations:\n" +
|
|
||||||
" work Combined status -> commit -> push workflow\n" +
|
Uses repos.yaml to discover repositories. Falls back to scanning
|
||||||
" health Quick repo health summary\n" +
|
the current directory if no registry is found.
|
||||||
" commit Claude-assisted commit messages\n" +
|
|
||||||
" push Push repos with unpushed commits\n" +
|
Git Operations:
|
||||||
" pull Pull repos behind remote\n\n" +
|
work Combined status -> commit -> push workflow
|
||||||
"GitHub Integration (requires gh CLI):\n" +
|
health Quick repo health summary
|
||||||
" issues List open issues across repos\n" +
|
commit Claude-assisted commit messages
|
||||||
" reviews List PRs awaiting review\n" +
|
push Push repos with unpushed commits
|
||||||
" ci Check GitHub Actions status\n" +
|
pull Pull repos behind remote
|
||||||
" impact Analyse dependency impact\n\n" +
|
|
||||||
"Dev Environment:\n" +
|
GitHub Integration (requires gh CLI):
|
||||||
" install Download dev environment image\n" +
|
issues List open issues across repos
|
||||||
" boot Start dev environment VM\n" +
|
reviews List PRs awaiting review
|
||||||
" stop Stop dev environment VM\n" +
|
ci Check GitHub Actions status
|
||||||
" shell Open shell in dev VM\n" +
|
impact Analyse dependency impact
|
||||||
" status Check dev VM status")
|
|
||||||
|
Dev Environment:
|
||||||
|
install Download dev environment image
|
||||||
|
boot Start dev environment VM
|
||||||
|
stop Stop dev environment VM
|
||||||
|
shell Open shell in dev VM
|
||||||
|
status Check dev VM status`,
|
||||||
|
}
|
||||||
|
root.AddCommand(devCmd)
|
||||||
|
|
||||||
// Git operations
|
// Git operations
|
||||||
addWorkCommand(devCmd)
|
addWorkCommand(devCmd)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
package dev
|
package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addAPICommands adds the 'api' command and its subcommands to the given parent command.
|
// addAPICommands adds the 'api' command and its subcommands to the given parent command.
|
||||||
func addAPICommands(parent *clir.Command) {
|
func addAPICommands(parent *cobra.Command) {
|
||||||
// Create the 'api' command
|
// Create the 'api' command
|
||||||
apiCmd := parent.NewSubCommand("api", "Tools for managing service APIs")
|
apiCmd := &cobra.Command{
|
||||||
|
Use: "api",
|
||||||
|
Short: "Tools for managing service APIs",
|
||||||
|
}
|
||||||
|
parent.AddCommand(apiCmd)
|
||||||
|
|
||||||
// Add the 'sync' command to 'api'
|
// Add the 'sync' command to 'api'
|
||||||
addSyncCommand(apiCmd)
|
addSyncCommand(apiCmd)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CI-specific styles
|
// CI-specific styles
|
||||||
|
|
@ -45,27 +45,35 @@ type WorkflowRun struct {
|
||||||
RepoName string `json:"-"`
|
RepoName string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CI command flags
|
||||||
|
var (
|
||||||
|
ciRegistryPath string
|
||||||
|
ciBranch string
|
||||||
|
ciFailedOnly bool
|
||||||
|
)
|
||||||
|
|
||||||
// addCICommand adds the 'ci' command to the given parent command.
|
// addCICommand adds the 'ci' command to the given parent command.
|
||||||
func addCICommand(parent *clir.Command) {
|
func addCICommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
ciCmd := &cobra.Command{
|
||||||
var branch string
|
Use: "ci",
|
||||||
var failedOnly bool
|
Short: "Check CI status across all repos",
|
||||||
|
Long: `Fetches GitHub Actions workflow status for all repos.
|
||||||
ciCmd := parent.NewSubCommand("ci", "Check CI status across all repos")
|
Shows latest run status for each repo.
|
||||||
ciCmd.LongDescription("Fetches GitHub Actions workflow status for all repos.\n" +
|
Requires the 'gh' CLI to be installed and authenticated.`,
|
||||||
"Shows latest run status for each repo.\n" +
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
"Requires the 'gh' CLI to be installed and authenticated.")
|
branch := ciBranch
|
||||||
|
|
||||||
ciCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
|
||||||
ciCmd.StringFlag("branch", "Filter by branch (default: main)", &branch)
|
|
||||||
ciCmd.BoolFlag("failed", "Show only failed runs", &failedOnly)
|
|
||||||
|
|
||||||
ciCmd.Action(func() error {
|
|
||||||
if branch == "" {
|
if branch == "" {
|
||||||
branch = "main"
|
branch = "main"
|
||||||
}
|
}
|
||||||
return runCI(registryPath, branch, failedOnly)
|
return runCI(ciRegistryPath, branch, ciFailedOnly)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ciCmd.Flags().StringVar(&ciRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
|
ciCmd.Flags().StringVarP(&ciBranch, "branch", "b", "main", "Filter by branch")
|
||||||
|
ciCmd.Flags().BoolVar(&ciFailedOnly, "failed", false, "Show only failed runs")
|
||||||
|
|
||||||
|
parent.AddCommand(ciCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCI(registryPath string, branch string, failedOnly bool) error {
|
func runCI(registryPath string, branch string, failedOnly bool) error {
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,31 @@ import (
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Commit command flags
|
||||||
|
var (
|
||||||
|
commitRegistryPath string
|
||||||
|
commitAll bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// addCommitCommand adds the 'commit' command to the given parent command.
|
// addCommitCommand adds the 'commit' command to the given parent command.
|
||||||
func addCommitCommand(parent *clir.Command) {
|
func addCommitCommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
commitCmd := &cobra.Command{
|
||||||
var all bool
|
Use: "commit",
|
||||||
|
Short: "Claude-assisted commits across repos",
|
||||||
|
Long: `Uses Claude to create commits for dirty repos.
|
||||||
|
Shows uncommitted changes and invokes Claude to generate commit messages.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runCommit(commitRegistryPath, commitAll)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
commitCmd := parent.NewSubCommand("commit", "Claude-assisted commits across repos")
|
commitCmd.Flags().StringVar(&commitRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
commitCmd.LongDescription("Uses Claude to create commits for dirty repos.\n" +
|
commitCmd.Flags().BoolVar(&commitAll, "all", false, "Commit all dirty repos without prompting")
|
||||||
"Shows uncommitted changes and invokes Claude to generate commit messages.")
|
|
||||||
|
|
||||||
commitCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
parent.AddCommand(commitCmd)
|
||||||
commitCmd.BoolFlag("all", "Commit all dirty repos without prompting", &all)
|
|
||||||
|
|
||||||
commitCmd.Action(func() error {
|
|
||||||
return runCommit(registryPath, all)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCommit(registryPath string, all bool) error {
|
func runCommit(registryPath string, all bool) error {
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,31 @@ import (
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Health command flags
|
||||||
|
var (
|
||||||
|
healthRegistryPath string
|
||||||
|
healthVerbose bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// addHealthCommand adds the 'health' command to the given parent command.
|
// addHealthCommand adds the 'health' command to the given parent command.
|
||||||
func addHealthCommand(parent *clir.Command) {
|
func addHealthCommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
healthCmd := &cobra.Command{
|
||||||
var verbose bool
|
Use: "health",
|
||||||
|
Short: "Quick health check across all repos",
|
||||||
|
Long: `Shows a summary of repository health:
|
||||||
|
total repos, dirty repos, unpushed commits, etc.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runHealth(healthRegistryPath, healthVerbose)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
healthCmd := parent.NewSubCommand("health", "Quick health check across all repos")
|
healthCmd.Flags().StringVar(&healthRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
healthCmd.LongDescription("Shows a summary of repository health:\n" +
|
healthCmd.Flags().BoolVarP(&healthVerbose, "verbose", "v", false, "Show detailed breakdown")
|
||||||
"total repos, dirty repos, unpushed commits, etc.")
|
|
||||||
|
|
||||||
healthCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
parent.AddCommand(healthCmd)
|
||||||
healthCmd.BoolFlag("verbose", "Show detailed breakdown", &verbose)
|
|
||||||
|
|
||||||
healthCmd.Action(func() error {
|
|
||||||
return runHealth(registryPath, verbose)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runHealth(registryPath string, verbose bool) error {
|
func runHealth(registryPath string, verbose bool) error {
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,12 @@ package dev
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Impact-specific styles
|
// Impact-specific styles
|
||||||
|
|
@ -24,31 +23,25 @@ var (
|
||||||
Foreground(lipgloss.Color("#22c55e")) // green-500
|
Foreground(lipgloss.Color("#22c55e")) // green-500
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Impact command flags
|
||||||
|
var impactRegistryPath string
|
||||||
|
|
||||||
// addImpactCommand adds the 'impact' command to the given parent command.
|
// addImpactCommand adds the 'impact' command to the given parent command.
|
||||||
func addImpactCommand(parent *clir.Command) {
|
func addImpactCommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
impactCmd := &cobra.Command{
|
||||||
|
Use: "impact <repo-name>",
|
||||||
impactCmd := parent.NewSubCommand("impact", "Show impact of changing a repo")
|
Short: "Show impact of changing a repo",
|
||||||
impactCmd.LongDescription("Analyzes the dependency graph to show which repos\n" +
|
Long: `Analyzes the dependency graph to show which repos
|
||||||
"would be affected by changes to the specified repo.")
|
would be affected by changes to the specified repo.`,
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
impactCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runImpact(impactRegistryPath, args[0])
|
||||||
impactCmd.Action(func() error {
|
},
|
||||||
args := os.Args[2:] // Skip "core" and "impact"
|
|
||||||
// Filter out flags
|
|
||||||
var repoName string
|
|
||||||
for _, arg := range args {
|
|
||||||
if arg[0] != '-' {
|
|
||||||
repoName = arg
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if repoName == "" {
|
impactCmd.Flags().StringVar(&impactRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
return fmt.Errorf("usage: core impact <repo-name>")
|
|
||||||
}
|
parent.AddCommand(impactCmd)
|
||||||
return runImpact(registryPath, repoName)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runImpact(registryPath string, repoName string) error {
|
func runImpact(registryPath string, repoName string) error {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Issue-specific styles
|
// Issue-specific styles
|
||||||
|
|
@ -62,26 +62,34 @@ type GitHubIssue struct {
|
||||||
RepoName string `json:"-"`
|
RepoName string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issues command flags
|
||||||
|
var (
|
||||||
|
issuesRegistryPath string
|
||||||
|
issuesLimit int
|
||||||
|
issuesAssignee string
|
||||||
|
)
|
||||||
|
|
||||||
// addIssuesCommand adds the 'issues' command to the given parent command.
|
// addIssuesCommand adds the 'issues' command to the given parent command.
|
||||||
func addIssuesCommand(parent *clir.Command) {
|
func addIssuesCommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
issuesCmd := &cobra.Command{
|
||||||
var limit int
|
Use: "issues",
|
||||||
var assignee string
|
Short: "List open issues across all repos",
|
||||||
|
Long: `Fetches open issues from GitHub for all repos in the registry.
|
||||||
issuesCmd := parent.NewSubCommand("issues", "List open issues across all repos")
|
Requires the 'gh' CLI to be installed and authenticated.`,
|
||||||
issuesCmd.LongDescription("Fetches open issues from GitHub for all repos in the registry.\n" +
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
"Requires the 'gh' CLI to be installed and authenticated.")
|
limit := issuesLimit
|
||||||
|
|
||||||
issuesCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
|
||||||
issuesCmd.IntFlag("limit", "Max issues per repo (default 10)", &limit)
|
|
||||||
issuesCmd.StringFlag("assignee", "Filter by assignee (use @me for yourself)", &assignee)
|
|
||||||
|
|
||||||
issuesCmd.Action(func() error {
|
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
limit = 10
|
limit = 10
|
||||||
}
|
}
|
||||||
return runIssues(registryPath, limit, assignee)
|
return runIssues(issuesRegistryPath, limit, issuesAssignee)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
issuesCmd.Flags().StringVar(&issuesRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
|
issuesCmd.Flags().IntVarP(&issuesLimit, "limit", "l", 10, "Max issues per repo")
|
||||||
|
issuesCmd.Flags().StringVarP(&issuesAssignee, "assignee", "a", "", "Filter by assignee (use @me for yourself)")
|
||||||
|
|
||||||
|
parent.AddCommand(issuesCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runIssues(registryPath string, limit int, assignee string) error {
|
func runIssues(registryPath string, limit int, assignee string) error {
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,31 @@ import (
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pull command flags
|
||||||
|
var (
|
||||||
|
pullRegistryPath string
|
||||||
|
pullAll bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// addPullCommand adds the 'pull' command to the given parent command.
|
// addPullCommand adds the 'pull' command to the given parent command.
|
||||||
func addPullCommand(parent *clir.Command) {
|
func addPullCommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
pullCmd := &cobra.Command{
|
||||||
var all bool
|
Use: "pull",
|
||||||
|
Short: "Pull updates across all repos",
|
||||||
|
Long: `Pulls updates for all repos.
|
||||||
|
By default only pulls repos that are behind. Use --all to pull all repos.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runPull(pullRegistryPath, pullAll)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pullCmd := parent.NewSubCommand("pull", "Pull updates across all repos")
|
pullCmd.Flags().StringVar(&pullRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
pullCmd.LongDescription("Pulls updates for all repos.\n" +
|
pullCmd.Flags().BoolVar(&pullAll, "all", false, "Pull all repos, not just those behind")
|
||||||
"By default only pulls repos that are behind. Use --all to pull all repos.")
|
|
||||||
|
|
||||||
pullCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
parent.AddCommand(pullCmd)
|
||||||
pullCmd.BoolFlag("all", "Pull all repos, not just those behind", &all)
|
|
||||||
|
|
||||||
pullCmd.Action(func() error {
|
|
||||||
return runPull(registryPath, all)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPull(registryPath string, all bool) error {
|
func runPull(registryPath string, all bool) error {
|
||||||
|
|
|
||||||
|
|
@ -8,24 +8,31 @@ import (
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Push command flags
|
||||||
|
var (
|
||||||
|
pushRegistryPath string
|
||||||
|
pushForce bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// addPushCommand adds the 'push' command to the given parent command.
|
// addPushCommand adds the 'push' command to the given parent command.
|
||||||
func addPushCommand(parent *clir.Command) {
|
func addPushCommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
pushCmd := &cobra.Command{
|
||||||
var force bool
|
Use: "push",
|
||||||
|
Short: "Push commits across all repos",
|
||||||
|
Long: `Pushes unpushed commits for all repos.
|
||||||
|
Shows repos with commits to push and confirms before pushing.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runPush(pushRegistryPath, pushForce)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
pushCmd := parent.NewSubCommand("push", "Push commits across all repos")
|
pushCmd.Flags().StringVar(&pushRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
pushCmd.LongDescription("Pushes unpushed commits for all repos.\n" +
|
pushCmd.Flags().BoolVarP(&pushForce, "force", "f", false, "Skip confirmation prompt")
|
||||||
"Shows repos with commits to push and confirms before pushing.")
|
|
||||||
|
|
||||||
pushCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
parent.AddCommand(pushCmd)
|
||||||
pushCmd.BoolFlag("force", "Skip confirmation prompt", &force)
|
|
||||||
|
|
||||||
pushCmd.Action(func() error {
|
|
||||||
return runPush(registryPath, force)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPush(registryPath string, force bool) error {
|
func runPush(registryPath string, force bool) error {
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PR-specific styles
|
// PR-specific styles
|
||||||
|
|
@ -67,24 +67,31 @@ type GitHubPR struct {
|
||||||
RepoName string `json:"-"`
|
RepoName string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reviews command flags
|
||||||
|
var (
|
||||||
|
reviewsRegistryPath string
|
||||||
|
reviewsAuthor string
|
||||||
|
reviewsShowAll bool
|
||||||
|
)
|
||||||
|
|
||||||
// addReviewsCommand adds the 'reviews' command to the given parent command.
|
// addReviewsCommand adds the 'reviews' command to the given parent command.
|
||||||
func addReviewsCommand(parent *clir.Command) {
|
func addReviewsCommand(parent *cobra.Command) {
|
||||||
var registryPath string
|
reviewsCmd := &cobra.Command{
|
||||||
var author string
|
Use: "reviews",
|
||||||
var showAll bool
|
Short: "List PRs needing review across all repos",
|
||||||
|
Long: `Fetches open PRs from GitHub for all repos in the registry.
|
||||||
|
Shows review status (approved, changes requested, pending).
|
||||||
|
Requires the 'gh' CLI to be installed and authenticated.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runReviews(reviewsRegistryPath, reviewsAuthor, reviewsShowAll)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
reviewsCmd := parent.NewSubCommand("reviews", "List PRs needing review across all repos")
|
reviewsCmd.Flags().StringVar(&reviewsRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
reviewsCmd.LongDescription("Fetches open PRs from GitHub for all repos in the registry.\n" +
|
reviewsCmd.Flags().StringVar(&reviewsAuthor, "author", "", "Filter by PR author")
|
||||||
"Shows review status (approved, changes requested, pending).\n" +
|
reviewsCmd.Flags().BoolVar(&reviewsShowAll, "all", false, "Show all PRs including drafts")
|
||||||
"Requires the 'gh' CLI to be installed and authenticated.")
|
|
||||||
|
|
||||||
reviewsCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
parent.AddCommand(reviewsCmd)
|
||||||
reviewsCmd.StringFlag("author", "Filter by PR author", &author)
|
|
||||||
reviewsCmd.BoolFlag("all", "Show all PRs including drafts", &showAll)
|
|
||||||
|
|
||||||
reviewsCmd.Action(func() error {
|
|
||||||
return runReviews(registryPath, author, showAll)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runReviews(registryPath string, author string, showAll bool) error {
|
func runReviews(registryPath string, author string, showAll bool) error {
|
||||||
|
|
|
||||||
|
|
@ -10,22 +10,29 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addSyncCommand adds the 'sync' command to the given parent command.
|
// addSyncCommand adds the 'sync' command to the given parent command.
|
||||||
func addSyncCommand(parent *clir.Command) {
|
func addSyncCommand(parent *cobra.Command) {
|
||||||
syncCmd := parent.NewSubCommand("sync", "Synchronizes the public service APIs with their internal implementations.")
|
syncCmd := &cobra.Command{
|
||||||
syncCmd.LongDescription("This command scans the 'pkg' directory for services and ensures that the\ntop-level public API for each service is in sync with its internal implementation.\nIt automatically generates the necessary Go files with type aliases.")
|
Use: "sync",
|
||||||
syncCmd.Action(func() error {
|
Short: "Synchronizes the public service APIs with their internal implementations.",
|
||||||
|
Long: `This command scans the 'pkg' directory for services and ensures that the
|
||||||
|
top-level public API for each service is in sync with its internal implementation.
|
||||||
|
It automatically generates the necessary Go files with type aliases.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if err := runSync(); err != nil {
|
if err := runSync(); err != nil {
|
||||||
return fmt.Errorf("Error: %w", err)
|
return fmt.Errorf("Error: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println("Public APIs synchronized successfully.")
|
fmt.Println("Public APIs synchronized successfully.")
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(syncCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
type symbolInfo struct {
|
type symbolInfo struct {
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,12 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/devops"
|
"github.com/host-uk/core/pkg/devops"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMCommands adds the dev environment VM commands to the dev parent command.
|
// addVMCommands adds the dev environment VM commands to the dev parent command.
|
||||||
// These are added as direct subcommands: core dev install, core dev boot, etc.
|
// These are added as direct subcommands: core dev install, core dev boot, etc.
|
||||||
func addVMCommands(parent *clir.Command) {
|
func addVMCommands(parent *cobra.Command) {
|
||||||
addVMInstallCommand(parent)
|
addVMInstallCommand(parent)
|
||||||
addVMBootCommand(parent)
|
addVMBootCommand(parent)
|
||||||
addVMStopCommand(parent)
|
addVMStopCommand(parent)
|
||||||
|
|
@ -25,17 +25,23 @@ func addVMCommands(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMInstallCommand adds the 'dev install' command.
|
// addVMInstallCommand adds the 'dev install' command.
|
||||||
func addVMInstallCommand(parent *clir.Command) {
|
func addVMInstallCommand(parent *cobra.Command) {
|
||||||
installCmd := parent.NewSubCommand("install", "Download and install the dev environment image")
|
installCmd := &cobra.Command{
|
||||||
installCmd.LongDescription("Downloads the platform-specific dev environment image.\n\n" +
|
Use: "install",
|
||||||
"The image includes Go, PHP, Node.js, Python, Docker, and Claude CLI.\n" +
|
Short: "Download and install the dev environment image",
|
||||||
"Downloads are cached at ~/.core/images/\n\n" +
|
Long: `Downloads the platform-specific dev environment image.
|
||||||
"Examples:\n" +
|
|
||||||
" core dev install")
|
|
||||||
|
|
||||||
installCmd.Action(func() error {
|
The image includes Go, PHP, Node.js, Python, Docker, and Claude CLI.
|
||||||
|
Downloads are cached at ~/.core/images/
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
core dev install`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runVMInstall()
|
return runVMInstall()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(installCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMInstall() error {
|
func runVMInstall() error {
|
||||||
|
|
@ -85,26 +91,34 @@ func runVMInstall() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VM boot command flags
|
||||||
|
var (
|
||||||
|
vmBootMemory int
|
||||||
|
vmBootCPUs int
|
||||||
|
vmBootFresh bool
|
||||||
|
)
|
||||||
|
|
||||||
// addVMBootCommand adds the 'devops boot' command.
|
// addVMBootCommand adds the 'devops boot' command.
|
||||||
func addVMBootCommand(parent *clir.Command) {
|
func addVMBootCommand(parent *cobra.Command) {
|
||||||
var memory int
|
bootCmd := &cobra.Command{
|
||||||
var cpus int
|
Use: "boot",
|
||||||
var fresh bool
|
Short: "Start the dev environment",
|
||||||
|
Long: `Boots the dev environment VM.
|
||||||
|
|
||||||
bootCmd := parent.NewSubCommand("boot", "Start the dev environment")
|
Examples:
|
||||||
bootCmd.LongDescription("Boots the dev environment VM.\n\n" +
|
core dev boot
|
||||||
"Examples:\n" +
|
core dev boot --memory 8192 --cpus 4
|
||||||
" core dev boot\n" +
|
core dev boot --fresh`,
|
||||||
" core dev boot --memory 8192 --cpus 4\n" +
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
" core dev boot --fresh")
|
return runVMBoot(vmBootMemory, vmBootCPUs, vmBootFresh)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
bootCmd.IntFlag("memory", "Memory in MB (default: 4096)", &memory)
|
bootCmd.Flags().IntVar(&vmBootMemory, "memory", 0, "Memory in MB (default: 4096)")
|
||||||
bootCmd.IntFlag("cpus", "Number of CPUs (default: 2)", &cpus)
|
bootCmd.Flags().IntVar(&vmBootCPUs, "cpus", 0, "Number of CPUs (default: 2)")
|
||||||
bootCmd.BoolFlag("fresh", "Stop existing and start fresh", &fresh)
|
bootCmd.Flags().BoolVar(&vmBootFresh, "fresh", false, "Stop existing and start fresh")
|
||||||
|
|
||||||
bootCmd.Action(func() error {
|
parent.AddCommand(bootCmd)
|
||||||
return runVMBoot(memory, cpus, fresh)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMBoot(memory, cpus int, fresh bool) error {
|
func runVMBoot(memory, cpus int, fresh bool) error {
|
||||||
|
|
@ -145,15 +159,20 @@ func runVMBoot(memory, cpus int, fresh bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMStopCommand adds the 'devops stop' command.
|
// addVMStopCommand adds the 'devops stop' command.
|
||||||
func addVMStopCommand(parent *clir.Command) {
|
func addVMStopCommand(parent *cobra.Command) {
|
||||||
stopCmd := parent.NewSubCommand("stop", "Stop the dev environment")
|
stopCmd := &cobra.Command{
|
||||||
stopCmd.LongDescription("Stops the running dev environment VM.\n\n" +
|
Use: "stop",
|
||||||
"Examples:\n" +
|
Short: "Stop the dev environment",
|
||||||
" core dev stop")
|
Long: `Stops the running dev environment VM.
|
||||||
|
|
||||||
stopCmd.Action(func() error {
|
Examples:
|
||||||
|
core dev stop`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runVMStop()
|
return runVMStop()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(stopCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMStop() error {
|
func runVMStop() error {
|
||||||
|
|
@ -184,15 +203,20 @@ func runVMStop() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMStatusCommand adds the 'devops status' command.
|
// addVMStatusCommand adds the 'devops status' command.
|
||||||
func addVMStatusCommand(parent *clir.Command) {
|
func addVMStatusCommand(parent *cobra.Command) {
|
||||||
statusCmd := parent.NewSubCommand("vm-status", "Show dev environment status")
|
statusCmd := &cobra.Command{
|
||||||
statusCmd.LongDescription("Shows the current status of the dev environment.\n\n" +
|
Use: "vm-status",
|
||||||
"Examples:\n" +
|
Short: "Show dev environment status",
|
||||||
" core dev vm-status")
|
Long: `Shows the current status of the dev environment.
|
||||||
|
|
||||||
statusCmd.Action(func() error {
|
Examples:
|
||||||
|
core dev vm-status`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runVMStatus()
|
return runVMStatus()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(statusCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMStatus() error {
|
func runVMStatus() error {
|
||||||
|
|
@ -255,24 +279,30 @@ func formatVMUptime(d time.Duration) string {
|
||||||
return fmt.Sprintf("%dd %dh", int(d.Hours()/24), int(d.Hours())%24)
|
return fmt.Sprintf("%dd %dh", int(d.Hours()/24), int(d.Hours())%24)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VM shell command flags
|
||||||
|
var vmShellConsole bool
|
||||||
|
|
||||||
// addVMShellCommand adds the 'devops shell' command.
|
// addVMShellCommand adds the 'devops shell' command.
|
||||||
func addVMShellCommand(parent *clir.Command) {
|
func addVMShellCommand(parent *cobra.Command) {
|
||||||
var console bool
|
shellCmd := &cobra.Command{
|
||||||
|
Use: "shell [-- command...]",
|
||||||
|
Short: "Connect to the dev environment",
|
||||||
|
Long: `Opens an interactive shell in the dev environment.
|
||||||
|
|
||||||
shellCmd := parent.NewSubCommand("shell", "Connect to the dev environment")
|
Uses SSH by default, or serial console with --console.
|
||||||
shellCmd.LongDescription("Opens an interactive shell in the dev environment.\n\n" +
|
|
||||||
"Uses SSH by default, or serial console with --console.\n\n" +
|
|
||||||
"Examples:\n" +
|
|
||||||
" core dev shell\n" +
|
|
||||||
" core dev shell --console\n" +
|
|
||||||
" core dev shell -- ls -la")
|
|
||||||
|
|
||||||
shellCmd.BoolFlag("console", "Use serial console instead of SSH", &console)
|
Examples:
|
||||||
|
core dev shell
|
||||||
|
core dev shell --console
|
||||||
|
core dev shell -- ls -la`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runVMShell(vmShellConsole, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
shellCmd.Action(func() error {
|
shellCmd.Flags().BoolVar(&vmShellConsole, "console", false, "Use serial console instead of SSH")
|
||||||
args := shellCmd.OtherArgs()
|
|
||||||
return runVMShell(console, args)
|
parent.AddCommand(shellCmd)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMShell(console bool, command []string) error {
|
func runVMShell(console bool, command []string) error {
|
||||||
|
|
@ -290,25 +320,34 @@ func runVMShell(console bool, command []string) error {
|
||||||
return d.Shell(ctx, opts)
|
return d.Shell(ctx, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VM serve command flags
|
||||||
|
var (
|
||||||
|
vmServePort int
|
||||||
|
vmServePath string
|
||||||
|
)
|
||||||
|
|
||||||
// addVMServeCommand adds the 'devops serve' command.
|
// addVMServeCommand adds the 'devops serve' command.
|
||||||
func addVMServeCommand(parent *clir.Command) {
|
func addVMServeCommand(parent *cobra.Command) {
|
||||||
var port int
|
serveCmd := &cobra.Command{
|
||||||
var path string
|
Use: "serve",
|
||||||
|
Short: "Mount project and start dev server",
|
||||||
|
Long: `Mounts the current project into the dev environment and starts a dev server.
|
||||||
|
|
||||||
serveCmd := parent.NewSubCommand("serve", "Mount project and start dev server")
|
Auto-detects the appropriate serve command based on project files.
|
||||||
serveCmd.LongDescription("Mounts the current project into the dev environment and starts a dev server.\n\n" +
|
|
||||||
"Auto-detects the appropriate serve command based on project files.\n\n" +
|
|
||||||
"Examples:\n" +
|
|
||||||
" core dev serve\n" +
|
|
||||||
" core dev serve --port 3000\n" +
|
|
||||||
" core dev serve --path public")
|
|
||||||
|
|
||||||
serveCmd.IntFlag("port", "Port to serve on (default: 8000)", &port)
|
Examples:
|
||||||
serveCmd.StringFlag("path", "Subdirectory to serve", &path)
|
core dev serve
|
||||||
|
core dev serve --port 3000
|
||||||
|
core dev serve --path public`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runVMServe(vmServePort, vmServePath)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
serveCmd.Action(func() error {
|
serveCmd.Flags().IntVarP(&vmServePort, "port", "p", 0, "Port to serve on (default: 8000)")
|
||||||
return runVMServe(port, path)
|
serveCmd.Flags().StringVar(&vmServePath, "path", "", "Subdirectory to serve")
|
||||||
})
|
|
||||||
|
parent.AddCommand(serveCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMServe(port int, path string) error {
|
func runVMServe(port int, path string) error {
|
||||||
|
|
@ -331,24 +370,30 @@ func runVMServe(port int, path string) error {
|
||||||
return d.Serve(ctx, projectDir, opts)
|
return d.Serve(ctx, projectDir, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VM test command flags
|
||||||
|
var vmTestName string
|
||||||
|
|
||||||
// addVMTestCommand adds the 'devops test' command.
|
// addVMTestCommand adds the 'devops test' command.
|
||||||
func addVMTestCommand(parent *clir.Command) {
|
func addVMTestCommand(parent *cobra.Command) {
|
||||||
var name string
|
testCmd := &cobra.Command{
|
||||||
|
Use: "test [-- command...]",
|
||||||
|
Short: "Run tests in the dev environment",
|
||||||
|
Long: `Runs tests in the dev environment.
|
||||||
|
|
||||||
testCmd := parent.NewSubCommand("test", "Run tests in the dev environment")
|
Auto-detects the test command based on project files, or uses .core/test.yaml.
|
||||||
testCmd.LongDescription("Runs tests in the dev environment.\n\n" +
|
|
||||||
"Auto-detects the test command based on project files, or uses .core/test.yaml.\n\n" +
|
|
||||||
"Examples:\n" +
|
|
||||||
" core dev test\n" +
|
|
||||||
" core dev test --name integration\n" +
|
|
||||||
" core dev test -- go test -v ./...")
|
|
||||||
|
|
||||||
testCmd.StringFlag("name", "Run named test command from .core/test.yaml", &name)
|
Examples:
|
||||||
|
core dev test
|
||||||
|
core dev test --name integration
|
||||||
|
core dev test -- go test -v ./...`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runVMTest(vmTestName, args)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
testCmd.Action(func() error {
|
testCmd.Flags().StringVarP(&vmTestName, "name", "n", "", "Run named test command from .core/test.yaml")
|
||||||
args := testCmd.OtherArgs()
|
|
||||||
return runVMTest(name, args)
|
parent.AddCommand(testCmd)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMTest(name string, command []string) error {
|
func runVMTest(name string, command []string) error {
|
||||||
|
|
@ -371,34 +416,44 @@ func runVMTest(name string, command []string) error {
|
||||||
return d.Test(ctx, projectDir, opts)
|
return d.Test(ctx, projectDir, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VM claude command flags
|
||||||
|
var (
|
||||||
|
vmClaudeNoAuth bool
|
||||||
|
vmClaudeModel string
|
||||||
|
vmClaudeAuthFlags []string
|
||||||
|
)
|
||||||
|
|
||||||
// addVMClaudeCommand adds the 'devops claude' command.
|
// addVMClaudeCommand adds the 'devops claude' command.
|
||||||
func addVMClaudeCommand(parent *clir.Command) {
|
func addVMClaudeCommand(parent *cobra.Command) {
|
||||||
var noAuth bool
|
claudeCmd := &cobra.Command{
|
||||||
var model string
|
Use: "claude",
|
||||||
var authFlags []string
|
Short: "Start sandboxed Claude session",
|
||||||
|
Long: `Starts a Claude Code session inside the dev environment sandbox.
|
||||||
|
|
||||||
claudeCmd := parent.NewSubCommand("claude", "Start sandboxed Claude session")
|
Provides isolation while forwarding selected credentials.
|
||||||
claudeCmd.LongDescription("Starts a Claude Code session inside the dev environment sandbox.\n\n" +
|
Auto-boots the dev environment if not running.
|
||||||
"Provides isolation while forwarding selected credentials.\n" +
|
|
||||||
"Auto-boots the dev environment if not running.\n\n" +
|
|
||||||
"Auth options (default: all):\n" +
|
|
||||||
" gh - GitHub CLI auth\n" +
|
|
||||||
" anthropic - Anthropic API key\n" +
|
|
||||||
" ssh - SSH agent forwarding\n" +
|
|
||||||
" git - Git config (name, email)\n\n" +
|
|
||||||
"Examples:\n" +
|
|
||||||
" core dev claude\n" +
|
|
||||||
" core dev claude --model opus\n" +
|
|
||||||
" core dev claude --auth gh,anthropic\n" +
|
|
||||||
" core dev claude --no-auth")
|
|
||||||
|
|
||||||
claudeCmd.BoolFlag("no-auth", "Don't forward any auth credentials", &noAuth)
|
Auth options (default: all):
|
||||||
claudeCmd.StringFlag("model", "Model to use (opus, sonnet)", &model)
|
gh - GitHub CLI auth
|
||||||
claudeCmd.StringsFlag("auth", "Selective auth forwarding (gh,anthropic,ssh,git)", &authFlags)
|
anthropic - Anthropic API key
|
||||||
|
ssh - SSH agent forwarding
|
||||||
|
git - Git config (name, email)
|
||||||
|
|
||||||
claudeCmd.Action(func() error {
|
Examples:
|
||||||
return runVMClaude(noAuth, model, authFlags)
|
core dev claude
|
||||||
})
|
core dev claude --model opus
|
||||||
|
core dev claude --auth gh,anthropic
|
||||||
|
core dev claude --no-auth`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runVMClaude(vmClaudeNoAuth, vmClaudeModel, vmClaudeAuthFlags)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
claudeCmd.Flags().BoolVar(&vmClaudeNoAuth, "no-auth", false, "Don't forward any auth credentials")
|
||||||
|
claudeCmd.Flags().StringVarP(&vmClaudeModel, "model", "m", "", "Model to use (opus, sonnet)")
|
||||||
|
claudeCmd.Flags().StringSliceVar(&vmClaudeAuthFlags, "auth", nil, "Selective auth forwarding (gh,anthropic,ssh,git)")
|
||||||
|
|
||||||
|
parent.AddCommand(claudeCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMClaude(noAuth bool, model string, authFlags []string) error {
|
func runVMClaude(noAuth bool, model string, authFlags []string) error {
|
||||||
|
|
@ -422,21 +477,27 @@ func runVMClaude(noAuth bool, model string, authFlags []string) error {
|
||||||
return d.Claude(ctx, projectDir, opts)
|
return d.Claude(ctx, projectDir, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// VM update command flags
|
||||||
|
var vmUpdateApply bool
|
||||||
|
|
||||||
// addVMUpdateCommand adds the 'devops update' command.
|
// addVMUpdateCommand adds the 'devops update' command.
|
||||||
func addVMUpdateCommand(parent *clir.Command) {
|
func addVMUpdateCommand(parent *cobra.Command) {
|
||||||
var apply bool
|
updateCmd := &cobra.Command{
|
||||||
|
Use: "update",
|
||||||
|
Short: "Check for and apply updates",
|
||||||
|
Long: `Checks for dev environment updates and optionally applies them.
|
||||||
|
|
||||||
updateCmd := parent.NewSubCommand("update", "Check for and apply updates")
|
Examples:
|
||||||
updateCmd.LongDescription("Checks for dev environment updates and optionally applies them.\n\n" +
|
core dev update
|
||||||
"Examples:\n" +
|
core dev update --apply`,
|
||||||
" core dev update\n" +
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
" core dev update --apply")
|
return runVMUpdate(vmUpdateApply)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
updateCmd.BoolFlag("apply", "Download and apply the update", &apply)
|
updateCmd.Flags().BoolVar(&vmUpdateApply, "apply", false, "Download and apply the update")
|
||||||
|
|
||||||
updateCmd.Action(func() error {
|
parent.AddCommand(updateCmd)
|
||||||
return runVMUpdate(apply)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runVMUpdate(apply bool) error {
|
func runVMUpdate(apply bool) error {
|
||||||
|
|
|
||||||
|
|
@ -13,27 +13,35 @@ import (
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/host-uk/core/pkg/git"
|
"github.com/host-uk/core/pkg/git"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Work command flags
|
||||||
|
var (
|
||||||
|
workStatusOnly bool
|
||||||
|
workAutoCommit bool
|
||||||
|
workRegistryPath string
|
||||||
)
|
)
|
||||||
|
|
||||||
// addWorkCommand adds the 'work' command to the given parent command.
|
// addWorkCommand adds the 'work' command to the given parent command.
|
||||||
func addWorkCommand(parent *clir.Command) {
|
func addWorkCommand(parent *cobra.Command) {
|
||||||
var statusOnly bool
|
workCmd := &cobra.Command{
|
||||||
var autoCommit bool
|
Use: "work",
|
||||||
var registryPath string
|
Short: "Multi-repo git operations",
|
||||||
|
Long: `Manage git status, commits, and pushes across multiple repositories.
|
||||||
|
|
||||||
workCmd := parent.NewSubCommand("work", "Multi-repo git operations")
|
Reads repos.yaml to discover repositories and their relationships.
|
||||||
workCmd.LongDescription("Manage git status, commits, and pushes across multiple repositories.\n\n" +
|
Shows status, optionally commits with Claude, and pushes changes.`,
|
||||||
"Reads repos.yaml to discover repositories and their relationships.\n" +
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
"Shows status, optionally commits with Claude, and pushes changes.")
|
return runWork(workRegistryPath, workStatusOnly, workAutoCommit)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
workCmd.BoolFlag("status", "Show status only, don't push", &statusOnly)
|
workCmd.Flags().BoolVar(&workStatusOnly, "status", false, "Show status only, don't push")
|
||||||
workCmd.BoolFlag("commit", "Use Claude to commit dirty repos before pushing", &autoCommit)
|
workCmd.Flags().BoolVar(&workAutoCommit, "commit", false, "Use Claude to commit dirty repos before pushing")
|
||||||
workCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
workCmd.Flags().StringVar(&workRegistryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
|
|
||||||
workCmd.Action(func() error {
|
parent.AddCommand(workCmd)
|
||||||
return runWork(registryPath, statusOnly, autoCommit)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
func runWork(registryPath string, statusOnly, autoCommit bool) error {
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
// to a central location for unified documentation builds.
|
// to a central location for unified documentation builds.
|
||||||
package docs
|
package docs
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'docs' command and all subcommands.
|
// AddCommands registers the 'docs' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddDocsCommand(app)
|
root.AddCommand(docsCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ package docs
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style and utility aliases from shared
|
// Style and utility aliases from shared
|
||||||
|
|
@ -29,13 +29,14 @@ var (
|
||||||
Foreground(lipgloss.Color("#3b82f6")) // blue-500
|
Foreground(lipgloss.Color("#3b82f6")) // blue-500
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddDocsCommand adds the 'docs' command to the given parent command.
|
var docsCmd = &cobra.Command{
|
||||||
func AddDocsCommand(parent *clir.Cli) {
|
Use: "docs",
|
||||||
docsCmd := parent.NewSubCommand("docs", "Documentation management")
|
Short: "Documentation management",
|
||||||
docsCmd.LongDescription("Manage documentation across all repos.\n" +
|
Long: `Manage documentation across all repos.
|
||||||
"Scan for docs, check coverage, and sync to core-php/docs/packages/.")
|
Scan for docs, check coverage, and sync to core-php/docs/packages/.`,
|
||||||
|
}
|
||||||
// Add subcommands
|
|
||||||
addDocsSyncCommand(docsCmd)
|
func init() {
|
||||||
addDocsListCommand(docsCmd)
|
docsCmd.AddCommand(docsSyncCmd)
|
||||||
|
docsCmd.AddCommand(docsListCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,22 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addDocsListCommand(parent *clir.Command) {
|
// Flag variable for list command
|
||||||
var registryPath string
|
var docsListRegistryPath string
|
||||||
|
|
||||||
listCmd := parent.NewSubCommand("list", "List documentation across repos")
|
var docsListCmd = &cobra.Command{
|
||||||
listCmd.StringFlag("registry", "Path to repos.yaml", ®istryPath)
|
Use: "list",
|
||||||
|
Short: "List documentation across repos",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runDocsList(docsListRegistryPath)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
listCmd.Action(func() error {
|
func init() {
|
||||||
return runDocsList(registryPath)
|
docsListCmd.Flags().StringVar(&docsListRegistryPath, "registry", "", "Path to repos.yaml")
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDocsList(registryPath string) error {
|
func runDocsList(registryPath string) error {
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,28 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addDocsSyncCommand(parent *clir.Command) {
|
// Flag variables for sync command
|
||||||
var registryPath string
|
var (
|
||||||
var dryRun bool
|
docsSyncRegistryPath string
|
||||||
var outputDir string
|
docsSyncDryRun bool
|
||||||
|
docsSyncOutputDir string
|
||||||
|
)
|
||||||
|
|
||||||
syncCmd := parent.NewSubCommand("sync", "Sync documentation to core-php/docs/packages/")
|
var docsSyncCmd = &cobra.Command{
|
||||||
syncCmd.StringFlag("registry", "Path to repos.yaml", ®istryPath)
|
Use: "sync",
|
||||||
syncCmd.BoolFlag("dry-run", "Show what would be synced without copying", &dryRun)
|
Short: "Sync documentation to core-php/docs/packages/",
|
||||||
syncCmd.StringFlag("output", "Output directory (default: core-php/docs/packages)", &outputDir)
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runDocsSync(docsSyncRegistryPath, docsSyncOutputDir, docsSyncDryRun)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
syncCmd.Action(func() error {
|
func init() {
|
||||||
return runDocsSync(registryPath, outputDir, dryRun)
|
docsSyncCmd.Flags().StringVar(&docsSyncRegistryPath, "registry", "", "Path to repos.yaml")
|
||||||
})
|
docsSyncCmd.Flags().BoolVar(&docsSyncDryRun, "dry-run", false, "Show what would be synced without copying")
|
||||||
|
docsSyncCmd.Flags().StringVar(&docsSyncOutputDir, "output", "", "Output directory (default: core-php/docs/packages)")
|
||||||
}
|
}
|
||||||
|
|
||||||
// packageOutputName maps repo name to output folder name
|
// packageOutputName maps repo name to output folder name
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@
|
||||||
// Provides platform-specific installation instructions for missing tools.
|
// Provides platform-specific installation instructions for missing tools.
|
||||||
package doctor
|
package doctor
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'doctor' command and all subcommands.
|
// AddCommands registers the 'doctor' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddDoctorCommand(app)
|
root.AddCommand(doctorCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared
|
// Style aliases from shared
|
||||||
|
|
@ -15,19 +15,21 @@ var (
|
||||||
dimStyle = shared.DimStyle
|
dimStyle = shared.DimStyle
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddDoctorCommand adds the 'doctor' command to the given parent command.
|
// Flag variable for doctor command
|
||||||
func AddDoctorCommand(parent *clir.Cli) {
|
var doctorVerbose bool
|
||||||
var verbose bool
|
|
||||||
|
|
||||||
doctorCmd := parent.NewSubCommand("doctor", "Check development environment")
|
var doctorCmd = &cobra.Command{
|
||||||
doctorCmd.LongDescription("Checks that all required tools are installed and configured.\n" +
|
Use: "doctor",
|
||||||
"Run this before `core setup` to ensure your environment is ready.")
|
Short: "Check development environment",
|
||||||
|
Long: `Checks that all required tools are installed and configured.
|
||||||
|
Run this before 'core setup' to ensure your environment is ready.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runDoctor(doctorVerbose)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
doctorCmd.BoolFlag("verbose", "Show detailed version information", &verbose)
|
func init() {
|
||||||
|
doctorCmd.Flags().BoolVar(&doctorVerbose, "verbose", false, "Show detailed version information")
|
||||||
doctorCmd.Action(func() error {
|
|
||||||
return runDoctor(verbose)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDoctor(verbose bool) error {
|
func runDoctor(verbose bool) error {
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,9 @@
|
||||||
// Sets MACOSX_DEPLOYMENT_TARGET to suppress linker warnings on macOS.
|
// Sets MACOSX_DEPLOYMENT_TARGET to suppress linker warnings on macOS.
|
||||||
package gocmd
|
package gocmd
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'go' command and all subcommands.
|
// AddCommands registers the 'go' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddGoCommands(app)
|
AddGoCommands(root)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
cmd/go/go.go
14
cmd/go/go.go
|
|
@ -5,7 +5,7 @@ package gocmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases for shared styles
|
// Style aliases for shared styles
|
||||||
|
|
@ -16,9 +16,11 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddGoCommands adds Go development commands.
|
// AddGoCommands adds Go development commands.
|
||||||
func AddGoCommands(parent *clir.Cli) {
|
func AddGoCommands(root *cobra.Command) {
|
||||||
goCmd := parent.NewSubCommand("go", "Go development tools")
|
goCmd := &cobra.Command{
|
||||||
goCmd.LongDescription("Go development tools with enhanced output and environment setup.\n\n" +
|
Use: "go",
|
||||||
|
Short: "Go development tools",
|
||||||
|
Long: "Go development tools with enhanced output and environment setup.\n\n" +
|
||||||
"Commands:\n" +
|
"Commands:\n" +
|
||||||
" test Run tests\n" +
|
" test Run tests\n" +
|
||||||
" cov Run tests with coverage report\n" +
|
" cov Run tests with coverage report\n" +
|
||||||
|
|
@ -26,8 +28,10 @@ func AddGoCommands(parent *clir.Cli) {
|
||||||
" lint Run golangci-lint\n" +
|
" lint Run golangci-lint\n" +
|
||||||
" install Install Go binary\n" +
|
" install Install Go binary\n" +
|
||||||
" mod Module management (tidy, download, verify)\n" +
|
" mod Module management (tidy, download, verify)\n" +
|
||||||
" work Workspace management")
|
" work Workspace management",
|
||||||
|
}
|
||||||
|
|
||||||
|
root.AddCommand(goCmd)
|
||||||
addGoTestCommand(goCmd)
|
addGoTestCommand(goCmd)
|
||||||
addGoCovCommand(goCmd)
|
addGoCovCommand(goCmd)
|
||||||
addGoFmtCommand(goCmd)
|
addGoFmtCommand(goCmd)
|
||||||
|
|
|
||||||
|
|
@ -4,74 +4,82 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGoFmtCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
fix bool
|
fmtFix bool
|
||||||
diff bool
|
fmtDiff bool
|
||||||
check bool
|
fmtCheck bool
|
||||||
)
|
)
|
||||||
|
|
||||||
fmtCmd := parent.NewSubCommand("fmt", "Format Go code")
|
func addGoFmtCommand(parent *cobra.Command) {
|
||||||
fmtCmd.LongDescription("Format Go code using gofmt or goimports.\n\n" +
|
fmtCmd := &cobra.Command{
|
||||||
|
Use: "fmt",
|
||||||
|
Short: "Format Go code",
|
||||||
|
Long: "Format Go code using gofmt or goimports.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core go fmt # Check formatting\n" +
|
" core go fmt # Check formatting\n" +
|
||||||
" core go fmt --fix # Fix formatting\n" +
|
" core go fmt --fix # Fix formatting\n" +
|
||||||
" core go fmt --diff # Show diff")
|
" core go fmt --diff # Show diff",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
fmtCmd.BoolFlag("fix", "Fix formatting in place", &fix)
|
fmtArgs := []string{}
|
||||||
fmtCmd.BoolFlag("diff", "Show diff of changes", &diff)
|
if fmtFix {
|
||||||
fmtCmd.BoolFlag("check", "Check only, exit 1 if not formatted", &check)
|
fmtArgs = append(fmtArgs, "-w")
|
||||||
|
|
||||||
fmtCmd.Action(func() error {
|
|
||||||
args := []string{}
|
|
||||||
if fix {
|
|
||||||
args = append(args, "-w")
|
|
||||||
}
|
}
|
||||||
if diff {
|
if fmtDiff {
|
||||||
args = append(args, "-d")
|
fmtArgs = append(fmtArgs, "-d")
|
||||||
}
|
}
|
||||||
if !fix && !diff {
|
if !fmtFix && !fmtDiff {
|
||||||
args = append(args, "-l")
|
fmtArgs = append(fmtArgs, "-l")
|
||||||
}
|
}
|
||||||
args = append(args, ".")
|
fmtArgs = append(fmtArgs, ".")
|
||||||
|
|
||||||
// Try goimports first, fall back to gofmt
|
// Try goimports first, fall back to gofmt
|
||||||
var cmd *exec.Cmd
|
var execCmd *exec.Cmd
|
||||||
if _, err := exec.LookPath("goimports"); err == nil {
|
if _, err := exec.LookPath("goimports"); err == nil {
|
||||||
cmd = exec.Command("goimports", args...)
|
execCmd = exec.Command("goimports", fmtArgs...)
|
||||||
} else {
|
} else {
|
||||||
cmd = exec.Command("gofmt", args...)
|
execCmd = exec.Command("gofmt", fmtArgs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return execCmd.Run()
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGoLintCommand(parent *clir.Command) {
|
fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, "Fix formatting in place")
|
||||||
var fix bool
|
fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, "Show diff of changes")
|
||||||
|
fmtCmd.Flags().BoolVar(&fmtCheck, "check", false, "Check only, exit 1 if not formatted")
|
||||||
|
|
||||||
lintCmd := parent.NewSubCommand("lint", "Run golangci-lint")
|
parent.AddCommand(fmtCmd)
|
||||||
lintCmd.LongDescription("Run golangci-lint on the codebase.\n\n" +
|
}
|
||||||
|
|
||||||
|
var lintFix bool
|
||||||
|
|
||||||
|
func addGoLintCommand(parent *cobra.Command) {
|
||||||
|
lintCmd := &cobra.Command{
|
||||||
|
Use: "lint",
|
||||||
|
Short: "Run golangci-lint",
|
||||||
|
Long: "Run golangci-lint on the codebase.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core go lint\n" +
|
" core go lint\n" +
|
||||||
" core go lint --fix")
|
" core go lint --fix",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
lintCmd.BoolFlag("fix", "Fix issues automatically", &fix)
|
lintArgs := []string{"run"}
|
||||||
|
if lintFix {
|
||||||
lintCmd.Action(func() error {
|
lintArgs = append(lintArgs, "--fix")
|
||||||
args := []string{"run"}
|
|
||||||
if fix {
|
|
||||||
args = append(args, "--fix")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("golangci-lint", args...)
|
execCmd := exec.Command("golangci-lint", lintArgs...)
|
||||||
cmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return execCmd.Run()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
lintCmd.Flags().BoolVar(&lintFix, "fix", false, "Fix issues automatically")
|
||||||
|
|
||||||
|
parent.AddCommand(lintCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,41 +9,45 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGoTestCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
coverage bool
|
testCoverage bool
|
||||||
pkg string
|
testPkg string
|
||||||
run string
|
testRun string
|
||||||
short bool
|
testShort bool
|
||||||
race bool
|
testRace bool
|
||||||
json bool
|
testJSON bool
|
||||||
verbose bool
|
testVerbose bool
|
||||||
)
|
)
|
||||||
|
|
||||||
testCmd := parent.NewSubCommand("test", "Run tests with coverage")
|
func addGoTestCommand(parent *cobra.Command) {
|
||||||
testCmd.LongDescription("Run Go tests with coverage reporting.\n\n" +
|
testCmd := &cobra.Command{
|
||||||
|
Use: "test",
|
||||||
|
Short: "Run tests with coverage",
|
||||||
|
Long: "Run Go tests with coverage reporting.\n\n" +
|
||||||
"Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings.\n" +
|
"Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings.\n" +
|
||||||
"Filters noisy output and provides colour-coded coverage.\n\n" +
|
"Filters noisy output and provides colour-coded coverage.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core go test\n" +
|
" core go test\n" +
|
||||||
" core go test --coverage\n" +
|
" core go test --coverage\n" +
|
||||||
" core go test --pkg ./pkg/crypt\n" +
|
" core go test --pkg ./pkg/crypt\n" +
|
||||||
" core go test --run TestHash")
|
" core go test --run TestHash",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runGoTest(testCoverage, testPkg, testRun, testShort, testRace, testJSON, testVerbose)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
testCmd.BoolFlag("coverage", "Show detailed per-package coverage", &coverage)
|
testCmd.Flags().BoolVar(&testCoverage, "coverage", false, "Show detailed per-package coverage")
|
||||||
testCmd.StringFlag("pkg", "Package to test (default: ./...)", &pkg)
|
testCmd.Flags().StringVar(&testPkg, "pkg", "", "Package to test (default: ./...)")
|
||||||
testCmd.StringFlag("run", "Run only tests matching regexp", &run)
|
testCmd.Flags().StringVar(&testRun, "run", "", "Run only tests matching regexp")
|
||||||
testCmd.BoolFlag("short", "Run only short tests", &short)
|
testCmd.Flags().BoolVar(&testShort, "short", false, "Run only short tests")
|
||||||
testCmd.BoolFlag("race", "Enable race detector", &race)
|
testCmd.Flags().BoolVar(&testRace, "race", false, "Enable race detector")
|
||||||
testCmd.BoolFlag("json", "Output JSON results", &json)
|
testCmd.Flags().BoolVar(&testJSON, "json", false, "Output JSON results")
|
||||||
testCmd.BoolFlag("v", "Verbose output", &verbose)
|
testCmd.Flags().BoolVarP(&testVerbose, "verbose", "v", false, "Verbose output")
|
||||||
|
|
||||||
testCmd.Action(func() error {
|
parent.AddCommand(testCmd)
|
||||||
return runGoTest(coverage, pkg, run, short, race, json, verbose)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose bool) error {
|
func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose bool) error {
|
||||||
|
|
@ -166,28 +170,25 @@ func parseOverallCoverage(output string) float64 {
|
||||||
return total / float64(len(matches))
|
return total / float64(len(matches))
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGoCovCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
pkg string
|
covPkg string
|
||||||
html bool
|
covHTML bool
|
||||||
open bool
|
covOpen bool
|
||||||
threshold float64
|
covThreshold float64
|
||||||
)
|
)
|
||||||
|
|
||||||
covCmd := parent.NewSubCommand("cov", "Run tests with coverage report")
|
func addGoCovCommand(parent *cobra.Command) {
|
||||||
covCmd.LongDescription("Run tests and generate coverage report.\n\n" +
|
covCmd := &cobra.Command{
|
||||||
|
Use: "cov",
|
||||||
|
Short: "Run tests with coverage report",
|
||||||
|
Long: "Run tests and generate coverage report.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core go cov # Run with coverage summary\n" +
|
" core go cov # Run with coverage summary\n" +
|
||||||
" core go cov --html # Generate HTML report\n" +
|
" core go cov --html # Generate HTML report\n" +
|
||||||
" core go cov --open # Generate and open HTML report\n" +
|
" core go cov --open # Generate and open HTML report\n" +
|
||||||
" core go cov --threshold 80 # Fail if coverage < 80%")
|
" core go cov --threshold 80 # Fail if coverage < 80%",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
covCmd.StringFlag("pkg", "Package to test (default: ./...)", &pkg)
|
pkg := covPkg
|
||||||
covCmd.BoolFlag("html", "Generate HTML coverage report", &html)
|
|
||||||
covCmd.BoolFlag("open", "Generate and open HTML report in browser", &open)
|
|
||||||
covCmd.Float64Flag("threshold", "Minimum coverage percentage (exit 1 if below)", &threshold)
|
|
||||||
|
|
||||||
covCmd.Action(func() error {
|
|
||||||
if pkg == "" {
|
if pkg == "" {
|
||||||
// Auto-discover packages with tests
|
// Auto-discover packages with tests
|
||||||
pkgs, err := findTestPackages(".")
|
pkgs, err := findTestPackages(".")
|
||||||
|
|
@ -221,18 +222,18 @@ func addGoCovCommand(parent *clir.Command) {
|
||||||
// Run tests with coverage
|
// Run tests with coverage
|
||||||
// We need to split pkg into individual arguments if it contains spaces
|
// We need to split pkg into individual arguments if it contains spaces
|
||||||
pkgArgs := strings.Fields(pkg)
|
pkgArgs := strings.Fields(pkg)
|
||||||
args := append([]string{"test", "-coverprofile=" + covPath, "-covermode=atomic"}, pkgArgs...)
|
cmdArgs := append([]string{"test", "-coverprofile=" + covPath, "-covermode=atomic"}, pkgArgs...)
|
||||||
|
|
||||||
cmd := exec.Command("go", args...)
|
goCmd := exec.Command("go", cmdArgs...)
|
||||||
cmd.Env = append(os.Environ(), "MACOSX_DEPLOYMENT_TARGET=26.0")
|
goCmd.Env = append(os.Environ(), "MACOSX_DEPLOYMENT_TARGET=26.0")
|
||||||
cmd.Stdout = os.Stdout
|
goCmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
goCmd.Stderr = os.Stderr
|
||||||
|
|
||||||
testErr := cmd.Run()
|
testErr := goCmd.Run()
|
||||||
|
|
||||||
// Get coverage percentage
|
// Get coverage percentage
|
||||||
covCmd := exec.Command("go", "tool", "cover", "-func="+covPath)
|
coverCmd := exec.Command("go", "tool", "cover", "-func="+covPath)
|
||||||
covOutput, err := covCmd.Output()
|
covOutput, err := coverCmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if testErr != nil {
|
if testErr != nil {
|
||||||
return testErr
|
return testErr
|
||||||
|
|
@ -266,7 +267,7 @@ func addGoCovCommand(parent *clir.Command) {
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render("Total:"), covStyle.Render(fmt.Sprintf("%.1f%%", totalCov)))
|
fmt.Printf(" %s %s\n", dimStyle.Render("Total:"), covStyle.Render(fmt.Sprintf("%.1f%%", totalCov)))
|
||||||
|
|
||||||
// Generate HTML if requested
|
// Generate HTML if requested
|
||||||
if html || open {
|
if covHTML || covOpen {
|
||||||
htmlPath := "coverage.html"
|
htmlPath := "coverage.html"
|
||||||
htmlCmd := exec.Command("go", "tool", "cover", "-html="+covPath, "-o="+htmlPath)
|
htmlCmd := exec.Command("go", "tool", "cover", "-html="+covPath, "-o="+htmlPath)
|
||||||
if err := htmlCmd.Run(); err != nil {
|
if err := htmlCmd.Run(); err != nil {
|
||||||
|
|
@ -274,7 +275,7 @@ func addGoCovCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render("HTML:"), htmlPath)
|
fmt.Printf(" %s %s\n", dimStyle.Render("HTML:"), htmlPath)
|
||||||
|
|
||||||
if open {
|
if covOpen {
|
||||||
// Open in browser
|
// Open in browser
|
||||||
var openCmd *exec.Cmd
|
var openCmd *exec.Cmd
|
||||||
switch {
|
switch {
|
||||||
|
|
@ -292,9 +293,9 @@ func addGoCovCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check threshold
|
// Check threshold
|
||||||
if threshold > 0 && totalCov < threshold {
|
if covThreshold > 0 && totalCov < covThreshold {
|
||||||
fmt.Printf("\n%s Coverage %.1f%% is below threshold %.1f%%\n",
|
fmt.Printf("\n%s Coverage %.1f%% is below threshold %.1f%%\n",
|
||||||
errorStyle.Render("FAIL"), totalCov, threshold)
|
errorStyle.Render("FAIL"), totalCov, covThreshold)
|
||||||
return fmt.Errorf("coverage below threshold")
|
return fmt.Errorf("coverage below threshold")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -304,7 +305,15 @@ func addGoCovCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s\n", successStyle.Render("OK"))
|
fmt.Printf("\n%s\n", successStyle.Render("OK"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
covCmd.Flags().StringVar(&covPkg, "pkg", "", "Package to test (default: ./...)")
|
||||||
|
covCmd.Flags().BoolVar(&covHTML, "html", false, "Generate HTML coverage report")
|
||||||
|
covCmd.Flags().BoolVar(&covOpen, "open", false, "Generate and open HTML report in browser")
|
||||||
|
covCmd.Flags().Float64Var(&covThreshold, "threshold", 0, "Minimum coverage percentage (exit 1 if below)")
|
||||||
|
|
||||||
|
parent.AddCommand(covCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findTestPackages(root string) ([]string, error) {
|
func findTestPackages(root string) ([]string, error) {
|
||||||
|
|
|
||||||
|
|
@ -6,27 +6,26 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addGoInstallCommand(parent *clir.Command) {
|
var (
|
||||||
var verbose bool
|
installVerbose bool
|
||||||
var noCgo bool
|
installNoCgo bool
|
||||||
|
)
|
||||||
|
|
||||||
installCmd := parent.NewSubCommand("install", "Install Go binary")
|
func addGoInstallCommand(parent *cobra.Command) {
|
||||||
installCmd.LongDescription("Install Go binary to $GOPATH/bin.\n\n" +
|
installCmd := &cobra.Command{
|
||||||
|
Use: "install [path]",
|
||||||
|
Short: "Install Go binary",
|
||||||
|
Long: "Install Go binary to $GOPATH/bin.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core go install # Install current module\n" +
|
" core go install # Install current module\n" +
|
||||||
" core go install ./cmd/core # Install specific path\n" +
|
" core go install ./cmd/core # Install specific path\n" +
|
||||||
" core go install --no-cgo # Pure Go (no C dependencies)\n" +
|
" core go install --no-cgo # Pure Go (no C dependencies)\n" +
|
||||||
" core go install -v # Verbose output")
|
" core go install -v # Verbose output",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
installCmd.BoolFlag("v", "Verbose output", &verbose)
|
|
||||||
installCmd.BoolFlag("no-cgo", "Disable CGO (CGO_ENABLED=0)", &noCgo)
|
|
||||||
|
|
||||||
installCmd.Action(func() error {
|
|
||||||
// Get install path from args or default to current dir
|
// Get install path from args or default to current dir
|
||||||
args := installCmd.OtherArgs()
|
|
||||||
installPath := "./..."
|
installPath := "./..."
|
||||||
if len(args) > 0 {
|
if len(args) > 0 {
|
||||||
installPath = args[0]
|
installPath = args[0]
|
||||||
|
|
@ -45,24 +44,24 @@ func addGoInstallCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("%s Installing\n", dimStyle.Render("Install:"))
|
fmt.Printf("%s Installing\n", dimStyle.Render("Install:"))
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render("Path:"), installPath)
|
fmt.Printf(" %s %s\n", dimStyle.Render("Path:"), installPath)
|
||||||
if noCgo {
|
if installNoCgo {
|
||||||
fmt.Printf(" %s %s\n", dimStyle.Render("CGO:"), "disabled")
|
fmt.Printf(" %s %s\n", dimStyle.Render("CGO:"), "disabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs := []string{"install"}
|
cmdArgs := []string{"install"}
|
||||||
if verbose {
|
if installVerbose {
|
||||||
cmdArgs = append(cmdArgs, "-v")
|
cmdArgs = append(cmdArgs, "-v")
|
||||||
}
|
}
|
||||||
cmdArgs = append(cmdArgs, installPath)
|
cmdArgs = append(cmdArgs, installPath)
|
||||||
|
|
||||||
cmd := exec.Command("go", cmdArgs...)
|
execCmd := exec.Command("go", cmdArgs...)
|
||||||
if noCgo {
|
if installNoCgo {
|
||||||
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
|
execCmd.Env = append(os.Environ(), "CGO_ENABLED=0")
|
||||||
}
|
}
|
||||||
cmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := execCmd.Run(); err != nil {
|
||||||
fmt.Printf("\n%s\n", errorStyle.Render("FAIL Install failed"))
|
fmt.Printf("\n%s\n", errorStyle.Render("FAIL Install failed"))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -77,95 +76,132 @@ func addGoInstallCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s Installed to %s\n", successStyle.Render("OK"), binDir)
|
fmt.Printf("\n%s Installed to %s\n", successStyle.Render("OK"), binDir)
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGoModCommand(parent *clir.Command) {
|
installCmd.Flags().BoolVarP(&installVerbose, "verbose", "v", false, "Verbose output")
|
||||||
modCmd := parent.NewSubCommand("mod", "Module management")
|
installCmd.Flags().BoolVar(&installNoCgo, "no-cgo", false, "Disable CGO (CGO_ENABLED=0)")
|
||||||
modCmd.LongDescription("Go module management commands.\n\n" +
|
|
||||||
|
parent.AddCommand(installCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addGoModCommand(parent *cobra.Command) {
|
||||||
|
modCmd := &cobra.Command{
|
||||||
|
Use: "mod",
|
||||||
|
Short: "Module management",
|
||||||
|
Long: "Go module management commands.\n\n" +
|
||||||
"Commands:\n" +
|
"Commands:\n" +
|
||||||
" tidy Add missing and remove unused modules\n" +
|
" tidy Add missing and remove unused modules\n" +
|
||||||
" download Download modules to local cache\n" +
|
" download Download modules to local cache\n" +
|
||||||
" verify Verify dependencies\n" +
|
" verify Verify dependencies\n" +
|
||||||
" graph Print module dependency graph")
|
" graph Print module dependency graph",
|
||||||
|
|
||||||
// tidy
|
|
||||||
tidyCmd := modCmd.NewSubCommand("tidy", "Tidy go.mod")
|
|
||||||
tidyCmd.Action(func() error {
|
|
||||||
cmd := exec.Command("go", "mod", "tidy")
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
})
|
|
||||||
|
|
||||||
// download
|
|
||||||
downloadCmd := modCmd.NewSubCommand("download", "Download modules")
|
|
||||||
downloadCmd.Action(func() error {
|
|
||||||
cmd := exec.Command("go", "mod", "download")
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
})
|
|
||||||
|
|
||||||
// verify
|
|
||||||
verifyCmd := modCmd.NewSubCommand("verify", "Verify dependencies")
|
|
||||||
verifyCmd.Action(func() error {
|
|
||||||
cmd := exec.Command("go", "mod", "verify")
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
})
|
|
||||||
|
|
||||||
// graph
|
|
||||||
graphCmd := modCmd.NewSubCommand("graph", "Print dependency graph")
|
|
||||||
graphCmd.Action(func() error {
|
|
||||||
cmd := exec.Command("go", "mod", "graph")
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addGoWorkCommand(parent *clir.Command) {
|
// tidy
|
||||||
workCmd := parent.NewSubCommand("work", "Workspace management")
|
tidyCmd := &cobra.Command{
|
||||||
workCmd.LongDescription("Go workspace management commands.\n\n" +
|
Use: "tidy",
|
||||||
|
Short: "Tidy go.mod",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
execCmd := exec.Command("go", "mod", "tidy")
|
||||||
|
execCmd.Stdout = os.Stdout
|
||||||
|
execCmd.Stderr = os.Stderr
|
||||||
|
return execCmd.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// download
|
||||||
|
downloadCmd := &cobra.Command{
|
||||||
|
Use: "download",
|
||||||
|
Short: "Download modules",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
execCmd := exec.Command("go", "mod", "download")
|
||||||
|
execCmd.Stdout = os.Stdout
|
||||||
|
execCmd.Stderr = os.Stderr
|
||||||
|
return execCmd.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify
|
||||||
|
verifyCmd := &cobra.Command{
|
||||||
|
Use: "verify",
|
||||||
|
Short: "Verify dependencies",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
execCmd := exec.Command("go", "mod", "verify")
|
||||||
|
execCmd.Stdout = os.Stdout
|
||||||
|
execCmd.Stderr = os.Stderr
|
||||||
|
return execCmd.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// graph
|
||||||
|
graphCmd := &cobra.Command{
|
||||||
|
Use: "graph",
|
||||||
|
Short: "Print dependency graph",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
execCmd := exec.Command("go", "mod", "graph")
|
||||||
|
execCmd.Stdout = os.Stdout
|
||||||
|
execCmd.Stderr = os.Stderr
|
||||||
|
return execCmd.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
modCmd.AddCommand(tidyCmd)
|
||||||
|
modCmd.AddCommand(downloadCmd)
|
||||||
|
modCmd.AddCommand(verifyCmd)
|
||||||
|
modCmd.AddCommand(graphCmd)
|
||||||
|
parent.AddCommand(modCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addGoWorkCommand(parent *cobra.Command) {
|
||||||
|
workCmd := &cobra.Command{
|
||||||
|
Use: "work",
|
||||||
|
Short: "Workspace management",
|
||||||
|
Long: "Go workspace management commands.\n\n" +
|
||||||
"Commands:\n" +
|
"Commands:\n" +
|
||||||
" sync Sync go.work with modules\n" +
|
" sync Sync go.work with modules\n" +
|
||||||
" init Initialize go.work\n" +
|
" init Initialize go.work\n" +
|
||||||
" use Add module to workspace")
|
" use Add module to workspace",
|
||||||
|
}
|
||||||
|
|
||||||
// sync
|
// sync
|
||||||
syncCmd := workCmd.NewSubCommand("sync", "Sync workspace")
|
syncCmd := &cobra.Command{
|
||||||
syncCmd.Action(func() error {
|
Use: "sync",
|
||||||
cmd := exec.Command("go", "work", "sync")
|
Short: "Sync workspace",
|
||||||
cmd.Stdout = os.Stdout
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cmd.Stderr = os.Stderr
|
execCmd := exec.Command("go", "work", "sync")
|
||||||
return cmd.Run()
|
execCmd.Stdout = os.Stdout
|
||||||
})
|
execCmd.Stderr = os.Stderr
|
||||||
|
return execCmd.Run()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// init
|
// init
|
||||||
initCmd := workCmd.NewSubCommand("init", "Initialize workspace")
|
initCmd := &cobra.Command{
|
||||||
initCmd.Action(func() error {
|
Use: "init",
|
||||||
cmd := exec.Command("go", "work", "init")
|
Short: "Initialize workspace",
|
||||||
cmd.Stdout = os.Stdout
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cmd.Stderr = os.Stderr
|
execCmd := exec.Command("go", "work", "init")
|
||||||
if err := cmd.Run(); err != nil {
|
execCmd.Stdout = os.Stdout
|
||||||
|
execCmd.Stderr = os.Stderr
|
||||||
|
if err := execCmd.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Auto-add current module if go.mod exists
|
// Auto-add current module if go.mod exists
|
||||||
if _, err := os.Stat("go.mod"); err == nil {
|
if _, err := os.Stat("go.mod"); err == nil {
|
||||||
cmd = exec.Command("go", "work", "use", ".")
|
execCmd = exec.Command("go", "work", "use", ".")
|
||||||
cmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return execCmd.Run()
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// use
|
// use
|
||||||
useCmd := workCmd.NewSubCommand("use", "Add module to workspace")
|
useCmd := &cobra.Command{
|
||||||
useCmd.Action(func() error {
|
Use: "use [modules...]",
|
||||||
args := useCmd.OtherArgs()
|
Short: "Add module to workspace",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
// Auto-detect modules
|
// Auto-detect modules
|
||||||
modules := findGoModules(".")
|
modules := findGoModules(".")
|
||||||
|
|
@ -173,10 +209,10 @@ func addGoWorkCommand(parent *clir.Command) {
|
||||||
return fmt.Errorf("no go.mod files found")
|
return fmt.Errorf("no go.mod files found")
|
||||||
}
|
}
|
||||||
for _, mod := range modules {
|
for _, mod := range modules {
|
||||||
cmd := exec.Command("go", "work", "use", mod)
|
execCmd := exec.Command("go", "work", "use", mod)
|
||||||
cmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
if err := cmd.Run(); err != nil {
|
if err := execCmd.Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf("Added %s\n", mod)
|
fmt.Printf("Added %s\n", mod)
|
||||||
|
|
@ -185,11 +221,17 @@ func addGoWorkCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdArgs := append([]string{"work", "use"}, args...)
|
cmdArgs := append([]string{"work", "use"}, args...)
|
||||||
cmd := exec.Command("go", cmdArgs...)
|
execCmd := exec.Command("go", cmdArgs...)
|
||||||
cmd.Stdout = os.Stdout
|
execCmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
execCmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return execCmd.Run()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
workCmd.AddCommand(syncCmd)
|
||||||
|
workCmd.AddCommand(initCmd)
|
||||||
|
workCmd.AddCommand(useCmd)
|
||||||
|
parent.AddCommand(workCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findGoModules(root string) []string {
|
func findGoModules(root string) []string {
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@
|
||||||
// - deploy:list: List recent deployments
|
// - deploy:list: List recent deployments
|
||||||
package php
|
package php
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'php' command and all subcommands.
|
// AddCommands registers the 'php' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddPHPCommands(app)
|
AddPHPCommands(root)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ package php
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared
|
// Style aliases from shared
|
||||||
|
|
@ -78,15 +78,19 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddPHPCommands adds PHP/Laravel development commands.
|
// AddPHPCommands adds PHP/Laravel development commands.
|
||||||
func AddPHPCommands(parent *clir.Cli) {
|
func AddPHPCommands(root *cobra.Command) {
|
||||||
phpCmd := parent.NewSubCommand("php", "Laravel/PHP development tools")
|
phpCmd := &cobra.Command{
|
||||||
phpCmd.LongDescription("Manage Laravel development environment with FrankenPHP.\n\n" +
|
Use: "php",
|
||||||
|
Short: "Laravel/PHP development tools",
|
||||||
|
Long: "Manage Laravel development environment with FrankenPHP.\n\n" +
|
||||||
"Services orchestrated:\n" +
|
"Services orchestrated:\n" +
|
||||||
" - FrankenPHP/Octane (port 8000, HTTPS on 443)\n" +
|
" - FrankenPHP/Octane (port 8000, HTTPS on 443)\n" +
|
||||||
" - Vite dev server (port 5173)\n" +
|
" - Vite dev server (port 5173)\n" +
|
||||||
" - Laravel Horizon (queue workers)\n" +
|
" - Laravel Horizon (queue workers)\n" +
|
||||||
" - Laravel Reverb (WebSocket, port 8080)\n" +
|
" - Laravel Reverb (WebSocket, port 8080)\n" +
|
||||||
" - Redis (port 6379)")
|
" - Redis (port 6379)",
|
||||||
|
}
|
||||||
|
root.AddCommand(phpCmd)
|
||||||
|
|
||||||
// Development
|
// Development
|
||||||
addPHPDevCommand(phpCmd)
|
addPHPDevCommand(phpCmd)
|
||||||
|
|
|
||||||
|
|
@ -7,43 +7,34 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
phppkg "github.com/host-uk/core/pkg/php"
|
phppkg "github.com/host-uk/core/pkg/php"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addPHPBuildCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
buildType string
|
buildType string
|
||||||
imageName string
|
buildImageName string
|
||||||
tag string
|
buildTag string
|
||||||
platform string
|
buildPlatform string
|
||||||
dockerfile string
|
buildDockerfile string
|
||||||
outputPath string
|
buildOutputPath string
|
||||||
format string
|
buildFormat string
|
||||||
template string
|
buildTemplate string
|
||||||
noCache bool
|
buildNoCache bool
|
||||||
)
|
)
|
||||||
|
|
||||||
buildCmd := parent.NewSubCommand("build", "Build Docker or LinuxKit image")
|
func addPHPBuildCommand(parent *cobra.Command) {
|
||||||
buildCmd.LongDescription("Build a production-ready container image for the PHP project.\n\n" +
|
buildCmd := &cobra.Command{
|
||||||
|
Use: "build",
|
||||||
|
Short: "Build Docker or LinuxKit image",
|
||||||
|
Long: "Build a production-ready container image for the PHP project.\n\n" +
|
||||||
"By default, builds a Docker image using FrankenPHP.\n" +
|
"By default, builds a Docker image using FrankenPHP.\n" +
|
||||||
"Use --type linuxkit to build a LinuxKit VM image instead.\n\n" +
|
"Use --type linuxkit to build a LinuxKit VM image instead.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php build # Build Docker image\n" +
|
" core php build # Build Docker image\n" +
|
||||||
" core php build --name myapp --tag v1.0 # Build with custom name/tag\n" +
|
" core php build --name myapp --tag v1.0 # Build with custom name/tag\n" +
|
||||||
" core php build --type linuxkit # Build LinuxKit image\n" +
|
" core php build --type linuxkit # Build LinuxKit image\n" +
|
||||||
" core php build --type linuxkit --format iso # Build ISO image")
|
" core php build --type linuxkit --format iso # Build ISO image",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
buildCmd.StringFlag("type", "Build type: docker (default) or linuxkit", &buildType)
|
|
||||||
buildCmd.StringFlag("name", "Image name (default: project directory name)", &imageName)
|
|
||||||
buildCmd.StringFlag("tag", "Image tag (default: latest)", &tag)
|
|
||||||
buildCmd.StringFlag("platform", "Target platform (e.g., linux/amd64, linux/arm64)", &platform)
|
|
||||||
buildCmd.StringFlag("dockerfile", "Path to custom Dockerfile", &dockerfile)
|
|
||||||
buildCmd.StringFlag("output", "Output path for LinuxKit image", &outputPath)
|
|
||||||
buildCmd.StringFlag("format", "LinuxKit output format: qcow2 (default), iso, raw, vmdk", &format)
|
|
||||||
buildCmd.StringFlag("template", "LinuxKit template name (default: server-php)", &template)
|
|
||||||
buildCmd.BoolFlag("no-cache", "Build without cache", &noCache)
|
|
||||||
|
|
||||||
buildCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -54,20 +45,33 @@ func addPHPBuildCommand(parent *clir.Command) {
|
||||||
switch strings.ToLower(buildType) {
|
switch strings.ToLower(buildType) {
|
||||||
case "linuxkit":
|
case "linuxkit":
|
||||||
return runPHPBuildLinuxKit(ctx, cwd, linuxKitBuildOptions{
|
return runPHPBuildLinuxKit(ctx, cwd, linuxKitBuildOptions{
|
||||||
OutputPath: outputPath,
|
OutputPath: buildOutputPath,
|
||||||
Format: format,
|
Format: buildFormat,
|
||||||
Template: template,
|
Template: buildTemplate,
|
||||||
})
|
})
|
||||||
default:
|
default:
|
||||||
return runPHPBuildDocker(ctx, cwd, dockerBuildOptions{
|
return runPHPBuildDocker(ctx, cwd, dockerBuildOptions{
|
||||||
ImageName: imageName,
|
ImageName: buildImageName,
|
||||||
Tag: tag,
|
Tag: buildTag,
|
||||||
Platform: platform,
|
Platform: buildPlatform,
|
||||||
Dockerfile: dockerfile,
|
Dockerfile: buildDockerfile,
|
||||||
NoCache: noCache,
|
NoCache: buildNoCache,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
buildCmd.Flags().StringVar(&buildType, "type", "", "Build type: docker (default) or linuxkit")
|
||||||
|
buildCmd.Flags().StringVar(&buildImageName, "name", "", "Image name (default: project directory name)")
|
||||||
|
buildCmd.Flags().StringVar(&buildTag, "tag", "", "Image tag (default: latest)")
|
||||||
|
buildCmd.Flags().StringVar(&buildPlatform, "platform", "", "Target platform (e.g., linux/amd64, linux/arm64)")
|
||||||
|
buildCmd.Flags().StringVar(&buildDockerfile, "dockerfile", "", "Path to custom Dockerfile")
|
||||||
|
buildCmd.Flags().StringVar(&buildOutputPath, "output", "", "Output path for LinuxKit image")
|
||||||
|
buildCmd.Flags().StringVar(&buildFormat, "format", "", "LinuxKit output format: qcow2 (default), iso, raw, vmdk")
|
||||||
|
buildCmd.Flags().StringVar(&buildTemplate, "template", "", "LinuxKit template name (default: server-php)")
|
||||||
|
buildCmd.Flags().BoolVar(&buildNoCache, "no-cache", false, "Build without cache")
|
||||||
|
|
||||||
|
parent.AddCommand(buildCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dockerBuildOptions struct {
|
type dockerBuildOptions struct {
|
||||||
|
|
@ -182,34 +186,28 @@ func runPHPBuildLinuxKit(ctx context.Context, projectDir string, opts linuxKitBu
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPServeCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
imageName string
|
serveImageName string
|
||||||
tag string
|
serveTag string
|
||||||
containerName string
|
serveContainerName string
|
||||||
port int
|
servePort int
|
||||||
httpsPort int
|
serveHTTPSPort int
|
||||||
detach bool
|
serveDetach bool
|
||||||
envFile string
|
serveEnvFile string
|
||||||
)
|
)
|
||||||
|
|
||||||
serveCmd := parent.NewSubCommand("serve", "Run production container")
|
func addPHPServeCommand(parent *cobra.Command) {
|
||||||
serveCmd.LongDescription("Run a production PHP container.\n\n" +
|
serveCmd := &cobra.Command{
|
||||||
|
Use: "serve",
|
||||||
|
Short: "Run production container",
|
||||||
|
Long: "Run a production PHP container.\n\n" +
|
||||||
"This starts the built Docker image in production mode.\n\n" +
|
"This starts the built Docker image in production mode.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php serve --name myapp # Run container\n" +
|
" core php serve --name myapp # Run container\n" +
|
||||||
" core php serve --name myapp -d # Run detached\n" +
|
" core php serve --name myapp -d # Run detached\n" +
|
||||||
" core php serve --name myapp --port 8080 # Custom port")
|
" core php serve --name myapp --port 8080 # Custom port",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
serveCmd.StringFlag("name", "Docker image name (required)", &imageName)
|
imageName := serveImageName
|
||||||
serveCmd.StringFlag("tag", "Image tag (default: latest)", &tag)
|
|
||||||
serveCmd.StringFlag("container", "Container name", &containerName)
|
|
||||||
serveCmd.IntFlag("port", "HTTP port (default: 80)", &port)
|
|
||||||
serveCmd.IntFlag("https-port", "HTTPS port (default: 443)", &httpsPort)
|
|
||||||
serveCmd.BoolFlag("d", "Run in detached mode", &detach)
|
|
||||||
serveCmd.StringFlag("env-file", "Path to environment file", &envFile)
|
|
||||||
|
|
||||||
serveCmd.Action(func() error {
|
|
||||||
if imageName == "" {
|
if imageName == "" {
|
||||||
// Try to detect from current directory
|
// Try to detect from current directory
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
|
|
@ -228,28 +226,28 @@ func addPHPServeCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := phppkg.ServeOptions{
|
opts := phppkg.ServeOptions{
|
||||||
ImageName: imageName,
|
ImageName: imageName,
|
||||||
Tag: tag,
|
Tag: serveTag,
|
||||||
ContainerName: containerName,
|
ContainerName: serveContainerName,
|
||||||
Port: port,
|
Port: servePort,
|
||||||
HTTPSPort: httpsPort,
|
HTTPSPort: serveHTTPSPort,
|
||||||
Detach: detach,
|
Detach: serveDetach,
|
||||||
EnvFile: envFile,
|
EnvFile: serveEnvFile,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("%s Running production container...\n\n", dimStyle.Render("PHP:"))
|
fmt.Printf("%s Running production container...\n\n", dimStyle.Render("PHP:"))
|
||||||
fmt.Printf("%s %s:%s\n", dimStyle.Render("Image:"), imageName, func() string {
|
fmt.Printf("%s %s:%s\n", dimStyle.Render("Image:"), imageName, func() string {
|
||||||
if tag == "" {
|
if serveTag == "" {
|
||||||
return "latest"
|
return "latest"
|
||||||
}
|
}
|
||||||
return tag
|
return serveTag
|
||||||
}())
|
}())
|
||||||
|
|
||||||
effectivePort := port
|
effectivePort := servePort
|
||||||
if effectivePort == 0 {
|
if effectivePort == 0 {
|
||||||
effectivePort = 80
|
effectivePort = 80
|
||||||
}
|
}
|
||||||
effectiveHTTPSPort := httpsPort
|
effectiveHTTPSPort := serveHTTPSPort
|
||||||
if effectiveHTTPSPort == 0 {
|
if effectiveHTTPSPort == 0 {
|
||||||
effectiveHTTPSPort = 443
|
effectiveHTTPSPort = 443
|
||||||
}
|
}
|
||||||
|
|
@ -262,27 +260,35 @@ func addPHPServeCommand(parent *clir.Command) {
|
||||||
return fmt.Errorf("failed to start container: %w", err)
|
return fmt.Errorf("failed to start container: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !detach {
|
if !serveDetach {
|
||||||
fmt.Printf("\n%s Container stopped\n", dimStyle.Render("PHP:"))
|
fmt.Printf("\n%s Container stopped\n", dimStyle.Render("PHP:"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPShellCommand(parent *clir.Command) {
|
serveCmd.Flags().StringVar(&serveImageName, "name", "", "Docker image name (required)")
|
||||||
shellCmd := parent.NewSubCommand("shell", "Open shell in running container")
|
serveCmd.Flags().StringVar(&serveTag, "tag", "", "Image tag (default: latest)")
|
||||||
shellCmd.LongDescription("Open an interactive shell in a running PHP container.\n\n" +
|
serveCmd.Flags().StringVar(&serveContainerName, "container", "", "Container name")
|
||||||
|
serveCmd.Flags().IntVar(&servePort, "port", 0, "HTTP port (default: 80)")
|
||||||
|
serveCmd.Flags().IntVar(&serveHTTPSPort, "https-port", 0, "HTTPS port (default: 443)")
|
||||||
|
serveCmd.Flags().BoolVarP(&serveDetach, "detach", "d", false, "Run in detached mode")
|
||||||
|
serveCmd.Flags().StringVar(&serveEnvFile, "env-file", "", "Path to environment file")
|
||||||
|
|
||||||
|
parent.AddCommand(serveCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func addPHPShellCommand(parent *cobra.Command) {
|
||||||
|
shellCmd := &cobra.Command{
|
||||||
|
Use: "shell [container]",
|
||||||
|
Short: "Open shell in running container",
|
||||||
|
Long: "Open an interactive shell in a running PHP container.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php shell abc123 # Shell into container by ID\n" +
|
" core php shell abc123 # Shell into container by ID\n" +
|
||||||
" core php shell myapp # Shell into container by name")
|
" core php shell myapp # Shell into container by name",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
shellCmd.Action(func() error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
args := shellCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("container ID or name is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
fmt.Printf("%s Opening shell in container %s...\n", dimStyle.Render("PHP:"), args[0])
|
fmt.Printf("%s Opening shell in container %s...\n", dimStyle.Render("PHP:"), args[0])
|
||||||
|
|
@ -292,5 +298,8 @@ func addPHPShellCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(shellCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
phppkg "github.com/host-uk/core/pkg/php"
|
phppkg "github.com/host-uk/core/pkg/php"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Deploy command styles
|
// Deploy command styles
|
||||||
|
|
@ -23,7 +23,7 @@ var (
|
||||||
Foreground(lipgloss.Color("#ef4444")) // red-500
|
Foreground(lipgloss.Color("#ef4444")) // red-500
|
||||||
)
|
)
|
||||||
|
|
||||||
func addPHPDeployCommands(parent *clir.Command) {
|
func addPHPDeployCommands(parent *cobra.Command) {
|
||||||
// Main deploy command
|
// Main deploy command
|
||||||
addPHPDeployCommand(parent)
|
addPHPDeployCommand(parent)
|
||||||
|
|
||||||
|
|
@ -37,15 +37,17 @@ func addPHPDeployCommands(parent *clir.Command) {
|
||||||
addPHPDeployListCommand(parent)
|
addPHPDeployListCommand(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPDeployCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
staging bool
|
deployStaging bool
|
||||||
force bool
|
deployForce bool
|
||||||
wait bool
|
deployWait bool
|
||||||
)
|
)
|
||||||
|
|
||||||
deployCmd := parent.NewSubCommand("deploy", "Deploy to Coolify")
|
func addPHPDeployCommand(parent *cobra.Command) {
|
||||||
deployCmd.LongDescription("Deploy the PHP application to Coolify.\n\n" +
|
deployCmd := &cobra.Command{
|
||||||
|
Use: "deploy",
|
||||||
|
Short: "Deploy to Coolify",
|
||||||
|
Long: "Deploy the PHP application to Coolify.\n\n" +
|
||||||
"Requires configuration in .env:\n" +
|
"Requires configuration in .env:\n" +
|
||||||
" COOLIFY_URL=https://coolify.example.com\n" +
|
" COOLIFY_URL=https://coolify.example.com\n" +
|
||||||
" COOLIFY_TOKEN=your-api-token\n" +
|
" COOLIFY_TOKEN=your-api-token\n" +
|
||||||
|
|
@ -55,20 +57,15 @@ func addPHPDeployCommand(parent *clir.Command) {
|
||||||
" core php deploy # Deploy to production\n" +
|
" core php deploy # Deploy to production\n" +
|
||||||
" core php deploy --staging # Deploy to staging\n" +
|
" core php deploy --staging # Deploy to staging\n" +
|
||||||
" core php deploy --force # Force deployment\n" +
|
" core php deploy --force # Force deployment\n" +
|
||||||
" core php deploy --wait # Wait for deployment to complete")
|
" core php deploy --wait # Wait for deployment to complete",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
deployCmd.BoolFlag("staging", "Deploy to staging environment", &staging)
|
|
||||||
deployCmd.BoolFlag("force", "Force deployment even if no changes detected", &force)
|
|
||||||
deployCmd.BoolFlag("wait", "Wait for deployment to complete", &wait)
|
|
||||||
|
|
||||||
deployCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := phppkg.EnvProduction
|
env := phppkg.EnvProduction
|
||||||
if staging {
|
if deployStaging {
|
||||||
env = phppkg.EnvStaging
|
env = phppkg.EnvStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,8 +76,8 @@ func addPHPDeployCommand(parent *clir.Command) {
|
||||||
opts := phppkg.DeployOptions{
|
opts := phppkg.DeployOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
Force: force,
|
Force: deployForce,
|
||||||
Wait: wait,
|
Wait: deployWait,
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := phppkg.Deploy(ctx, opts)
|
status, err := phppkg.Deploy(ctx, opts)
|
||||||
|
|
@ -90,7 +87,7 @@ func addPHPDeployCommand(parent *clir.Command) {
|
||||||
|
|
||||||
printDeploymentStatus(status)
|
printDeploymentStatus(status)
|
||||||
|
|
||||||
if wait {
|
if deployWait {
|
||||||
if phppkg.IsDeploymentSuccessful(status.Status) {
|
if phppkg.IsDeploymentSuccessful(status.Status) {
|
||||||
fmt.Printf("\n%s Deployment completed successfully\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Deployment completed successfully\n", successStyle.Render("Done:"))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -101,33 +98,38 @@ func addPHPDeployCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
deployCmd.Flags().BoolVar(&deployStaging, "staging", false, "Deploy to staging environment")
|
||||||
|
deployCmd.Flags().BoolVar(&deployForce, "force", false, "Force deployment even if no changes detected")
|
||||||
|
deployCmd.Flags().BoolVar(&deployWait, "wait", false, "Wait for deployment to complete")
|
||||||
|
|
||||||
|
parent.AddCommand(deployCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPDeployStatusCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
staging bool
|
deployStatusStaging bool
|
||||||
deploymentID string
|
deployStatusDeploymentID string
|
||||||
)
|
)
|
||||||
|
|
||||||
statusCmd := parent.NewSubCommand("deploy:status", "Show deployment status")
|
func addPHPDeployStatusCommand(parent *cobra.Command) {
|
||||||
statusCmd.LongDescription("Show the status of a deployment.\n\n" +
|
statusCmd := &cobra.Command{
|
||||||
|
Use: "deploy:status",
|
||||||
|
Short: "Show deployment status",
|
||||||
|
Long: "Show the status of a deployment.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php deploy:status # Latest production deployment\n" +
|
" core php deploy:status # Latest production deployment\n" +
|
||||||
" core php deploy:status --staging # Latest staging deployment\n" +
|
" core php deploy:status --staging # Latest staging deployment\n" +
|
||||||
" core php deploy:status --id abc123 # Specific deployment")
|
" core php deploy:status --id abc123 # Specific deployment",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
statusCmd.BoolFlag("staging", "Check staging environment", &staging)
|
|
||||||
statusCmd.StringFlag("id", "Specific deployment ID", &deploymentID)
|
|
||||||
|
|
||||||
statusCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := phppkg.EnvProduction
|
env := phppkg.EnvProduction
|
||||||
if staging {
|
if deployStatusStaging {
|
||||||
env = phppkg.EnvStaging
|
env = phppkg.EnvStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,7 +140,7 @@ func addPHPDeployStatusCommand(parent *clir.Command) {
|
||||||
opts := phppkg.StatusOptions{
|
opts := phppkg.StatusOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
DeploymentID: deploymentID,
|
DeploymentID: deployStatusDeploymentID,
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := phppkg.DeployStatus(ctx, opts)
|
status, err := phppkg.DeployStatus(ctx, opts)
|
||||||
|
|
@ -149,37 +151,40 @@ func addPHPDeployStatusCommand(parent *clir.Command) {
|
||||||
printDeploymentStatus(status)
|
printDeploymentStatus(status)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCmd.Flags().BoolVar(&deployStatusStaging, "staging", false, "Check staging environment")
|
||||||
|
statusCmd.Flags().StringVar(&deployStatusDeploymentID, "id", "", "Specific deployment ID")
|
||||||
|
|
||||||
|
parent.AddCommand(statusCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPDeployRollbackCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
staging bool
|
rollbackStaging bool
|
||||||
deploymentID string
|
rollbackDeploymentID string
|
||||||
wait bool
|
rollbackWait bool
|
||||||
)
|
)
|
||||||
|
|
||||||
rollbackCmd := parent.NewSubCommand("deploy:rollback", "Rollback to previous deployment")
|
func addPHPDeployRollbackCommand(parent *cobra.Command) {
|
||||||
rollbackCmd.LongDescription("Rollback to a previous deployment.\n\n" +
|
rollbackCmd := &cobra.Command{
|
||||||
|
Use: "deploy:rollback",
|
||||||
|
Short: "Rollback to previous deployment",
|
||||||
|
Long: "Rollback to a previous deployment.\n\n" +
|
||||||
"If no deployment ID is specified, rolls back to the most recent\n" +
|
"If no deployment ID is specified, rolls back to the most recent\n" +
|
||||||
"successful deployment.\n\n" +
|
"successful deployment.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php deploy:rollback # Rollback to previous\n" +
|
" core php deploy:rollback # Rollback to previous\n" +
|
||||||
" core php deploy:rollback --staging # Rollback staging\n" +
|
" core php deploy:rollback --staging # Rollback staging\n" +
|
||||||
" core php deploy:rollback --id abc123 # Rollback to specific deployment")
|
" core php deploy:rollback --id abc123 # Rollback to specific deployment",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
rollbackCmd.BoolFlag("staging", "Rollback staging environment", &staging)
|
|
||||||
rollbackCmd.StringFlag("id", "Specific deployment ID to rollback to", &deploymentID)
|
|
||||||
rollbackCmd.BoolFlag("wait", "Wait for rollback to complete", &wait)
|
|
||||||
|
|
||||||
rollbackCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := phppkg.EnvProduction
|
env := phppkg.EnvProduction
|
||||||
if staging {
|
if rollbackStaging {
|
||||||
env = phppkg.EnvStaging
|
env = phppkg.EnvStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,8 +195,8 @@ func addPHPDeployRollbackCommand(parent *clir.Command) {
|
||||||
opts := phppkg.RollbackOptions{
|
opts := phppkg.RollbackOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Environment: env,
|
Environment: env,
|
||||||
DeploymentID: deploymentID,
|
DeploymentID: rollbackDeploymentID,
|
||||||
Wait: wait,
|
Wait: rollbackWait,
|
||||||
}
|
}
|
||||||
|
|
||||||
status, err := phppkg.Rollback(ctx, opts)
|
status, err := phppkg.Rollback(ctx, opts)
|
||||||
|
|
@ -201,7 +206,7 @@ func addPHPDeployRollbackCommand(parent *clir.Command) {
|
||||||
|
|
||||||
printDeploymentStatus(status)
|
printDeploymentStatus(status)
|
||||||
|
|
||||||
if wait {
|
if rollbackWait {
|
||||||
if phppkg.IsDeploymentSuccessful(status.Status) {
|
if phppkg.IsDeploymentSuccessful(status.Status) {
|
||||||
fmt.Printf("\n%s Rollback completed successfully\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Rollback completed successfully\n", successStyle.Render("Done:"))
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -212,36 +217,42 @@ func addPHPDeployRollbackCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rollbackCmd.Flags().BoolVar(&rollbackStaging, "staging", false, "Rollback staging environment")
|
||||||
|
rollbackCmd.Flags().StringVar(&rollbackDeploymentID, "id", "", "Specific deployment ID to rollback to")
|
||||||
|
rollbackCmd.Flags().BoolVar(&rollbackWait, "wait", false, "Wait for rollback to complete")
|
||||||
|
|
||||||
|
parent.AddCommand(rollbackCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPDeployListCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
staging bool
|
deployListStaging bool
|
||||||
limit int
|
deployListLimit int
|
||||||
)
|
)
|
||||||
|
|
||||||
listCmd := parent.NewSubCommand("deploy:list", "List recent deployments")
|
func addPHPDeployListCommand(parent *cobra.Command) {
|
||||||
listCmd.LongDescription("List recent deployments.\n\n" +
|
listCmd := &cobra.Command{
|
||||||
|
Use: "deploy:list",
|
||||||
|
Short: "List recent deployments",
|
||||||
|
Long: "List recent deployments.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php deploy:list # List production deployments\n" +
|
" core php deploy:list # List production deployments\n" +
|
||||||
" core php deploy:list --staging # List staging deployments\n" +
|
" core php deploy:list --staging # List staging deployments\n" +
|
||||||
" core php deploy:list --limit 20 # List more deployments")
|
" core php deploy:list --limit 20 # List more deployments",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
listCmd.BoolFlag("staging", "List staging deployments", &staging)
|
|
||||||
listCmd.IntFlag("limit", "Number of deployments to list (default: 10)", &limit)
|
|
||||||
|
|
||||||
listCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
env := phppkg.EnvProduction
|
env := phppkg.EnvProduction
|
||||||
if staging {
|
if deployListStaging {
|
||||||
env = phppkg.EnvStaging
|
env = phppkg.EnvStaging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
limit := deployListLimit
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
limit = 10
|
limit = 10
|
||||||
}
|
}
|
||||||
|
|
@ -265,7 +276,13 @@ func addPHPDeployListCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
listCmd.Flags().BoolVar(&deployListStaging, "staging", false, "List staging deployments")
|
||||||
|
listCmd.Flags().IntVar(&deployListLimit, "limit", 0, "Number of deployments to list (default: 10)")
|
||||||
|
|
||||||
|
parent.AddCommand(listCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printDeploymentStatus(status *phppkg.DeploymentStatus) {
|
func printDeploymentStatus(status *phppkg.DeploymentStatus) {
|
||||||
|
|
|
||||||
|
|
@ -12,47 +12,51 @@ import (
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
phppkg "github.com/host-uk/core/pkg/php"
|
phppkg "github.com/host-uk/core/pkg/php"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addPHPDevCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
noVite bool
|
devNoVite bool
|
||||||
noHorizon bool
|
devNoHorizon bool
|
||||||
noReverb bool
|
devNoReverb bool
|
||||||
noRedis bool
|
devNoRedis bool
|
||||||
https bool
|
devHTTPS bool
|
||||||
domain string
|
devDomain string
|
||||||
port int
|
devPort int
|
||||||
)
|
)
|
||||||
|
|
||||||
devCmd := parent.NewSubCommand("dev", "Start Laravel development environment")
|
func addPHPDevCommand(parent *cobra.Command) {
|
||||||
devCmd.LongDescription("Starts all detected Laravel services.\n\n" +
|
devCmd := &cobra.Command{
|
||||||
|
Use: "dev",
|
||||||
|
Short: "Start Laravel development environment",
|
||||||
|
Long: "Starts all detected Laravel services.\n\n" +
|
||||||
"Auto-detects:\n" +
|
"Auto-detects:\n" +
|
||||||
" - Vite (vite.config.js/ts)\n" +
|
" - Vite (vite.config.js/ts)\n" +
|
||||||
" - Horizon (config/horizon.php)\n" +
|
" - Horizon (config/horizon.php)\n" +
|
||||||
" - Reverb (config/reverb.php)\n" +
|
" - Reverb (config/reverb.php)\n" +
|
||||||
" - Redis (from .env)")
|
" - Redis (from .env)",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
devCmd.BoolFlag("no-vite", "Skip Vite dev server", &noVite)
|
|
||||||
devCmd.BoolFlag("no-horizon", "Skip Laravel Horizon", &noHorizon)
|
|
||||||
devCmd.BoolFlag("no-reverb", "Skip Laravel Reverb", &noReverb)
|
|
||||||
devCmd.BoolFlag("no-redis", "Skip Redis server", &noRedis)
|
|
||||||
devCmd.BoolFlag("https", "Enable HTTPS with mkcert", &https)
|
|
||||||
devCmd.StringFlag("domain", "Domain for SSL certificate (default: from APP_URL or localhost)", &domain)
|
|
||||||
devCmd.IntFlag("port", "FrankenPHP port (default: 8000)", &port)
|
|
||||||
|
|
||||||
devCmd.Action(func() error {
|
|
||||||
return runPHPDev(phpDevOptions{
|
return runPHPDev(phpDevOptions{
|
||||||
NoVite: noVite,
|
NoVite: devNoVite,
|
||||||
NoHorizon: noHorizon,
|
NoHorizon: devNoHorizon,
|
||||||
NoReverb: noReverb,
|
NoReverb: devNoReverb,
|
||||||
NoRedis: noRedis,
|
NoRedis: devNoRedis,
|
||||||
HTTPS: https,
|
HTTPS: devHTTPS,
|
||||||
Domain: domain,
|
Domain: devDomain,
|
||||||
Port: port,
|
Port: devPort,
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
devCmd.Flags().BoolVar(&devNoVite, "no-vite", false, "Skip Vite dev server")
|
||||||
|
devCmd.Flags().BoolVar(&devNoHorizon, "no-horizon", false, "Skip Laravel Horizon")
|
||||||
|
devCmd.Flags().BoolVar(&devNoReverb, "no-reverb", false, "Skip Laravel Reverb")
|
||||||
|
devCmd.Flags().BoolVar(&devNoRedis, "no-redis", false, "Skip Redis server")
|
||||||
|
devCmd.Flags().BoolVar(&devHTTPS, "https", false, "Enable HTTPS with mkcert")
|
||||||
|
devCmd.Flags().StringVar(&devDomain, "domain", "", "Domain for SSL certificate (default: from APP_URL or localhost)")
|
||||||
|
devCmd.Flags().IntVar(&devPort, "port", 0, "FrankenPHP port (default: 8000)")
|
||||||
|
|
||||||
|
parent.AddCommand(devCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
type phpDevOptions struct {
|
type phpDevOptions struct {
|
||||||
|
|
@ -181,20 +185,26 @@ shutdown:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPLogsCommand(parent *clir.Command) {
|
var (
|
||||||
var follow bool
|
logsFollow bool
|
||||||
var service string
|
logsService string
|
||||||
|
)
|
||||||
|
|
||||||
logsCmd := parent.NewSubCommand("logs", "View service logs")
|
func addPHPLogsCommand(parent *cobra.Command) {
|
||||||
logsCmd.LongDescription("Stream logs from Laravel services.\n\n" +
|
logsCmd := &cobra.Command{
|
||||||
"Services: frankenphp, vite, horizon, reverb, redis")
|
Use: "logs",
|
||||||
|
Short: "View service logs",
|
||||||
|
Long: "Stream logs from Laravel services.\n\n" +
|
||||||
|
"Services: frankenphp, vite, horizon, reverb, redis",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runPHPLogs(logsService, logsFollow)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
logsCmd.BoolFlag("follow", "Follow log output", &follow)
|
logsCmd.Flags().BoolVar(&logsFollow, "follow", false, "Follow log output")
|
||||||
logsCmd.StringFlag("service", "Specific service (default: all)", &service)
|
logsCmd.Flags().StringVar(&logsService, "service", "", "Specific service (default: all)")
|
||||||
|
|
||||||
logsCmd.Action(func() error {
|
parent.AddCommand(logsCmd)
|
||||||
return runPHPLogs(service, follow)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPHPLogs(service string, follow bool) error {
|
func runPHPLogs(service string, follow bool) error {
|
||||||
|
|
@ -241,12 +251,16 @@ func runPHPLogs(service string, follow bool) error {
|
||||||
return scanner.Err()
|
return scanner.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPStopCommand(parent *clir.Command) {
|
func addPHPStopCommand(parent *cobra.Command) {
|
||||||
stopCmd := parent.NewSubCommand("stop", "Stop all Laravel services")
|
stopCmd := &cobra.Command{
|
||||||
|
Use: "stop",
|
||||||
stopCmd.Action(func() error {
|
Short: "Stop all Laravel services",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runPHPStop()
|
return runPHPStop()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(stopCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPHPStop() error {
|
func runPHPStop() error {
|
||||||
|
|
@ -268,12 +282,16 @@ func runPHPStop() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPStatusCommand(parent *clir.Command) {
|
func addPHPStatusCommand(parent *cobra.Command) {
|
||||||
statusCmd := parent.NewSubCommand("status", "Show service status")
|
statusCmd := &cobra.Command{
|
||||||
|
Use: "status",
|
||||||
statusCmd.Action(func() error {
|
Short: "Show service status",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runPHPStatus()
|
return runPHPStatus()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(statusCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPHPStatus() error {
|
func runPHPStatus() error {
|
||||||
|
|
@ -325,16 +343,20 @@ func runPHPStatus() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPSSLCommand(parent *clir.Command) {
|
var sslDomain string
|
||||||
var domain string
|
|
||||||
|
|
||||||
sslCmd := parent.NewSubCommand("ssl", "Setup SSL certificates with mkcert")
|
func addPHPSSLCommand(parent *cobra.Command) {
|
||||||
|
sslCmd := &cobra.Command{
|
||||||
|
Use: "ssl",
|
||||||
|
Short: "Setup SSL certificates with mkcert",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runPHPSSL(sslDomain)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
sslCmd.StringFlag("domain", "Domain for certificate (default: from APP_URL)", &domain)
|
sslCmd.Flags().StringVar(&sslDomain, "domain", "", "Domain for certificate (default: from APP_URL)")
|
||||||
|
|
||||||
sslCmd.Action(func() error {
|
parent.AddCommand(sslCmd)
|
||||||
return runPHPSSL(domain)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPHPSSL(domain string) error {
|
func runPHPSSL(domain string) error {
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,23 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
phppkg "github.com/host-uk/core/pkg/php"
|
phppkg "github.com/host-uk/core/pkg/php"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addPHPPackagesCommands(parent *clir.Command) {
|
func addPHPPackagesCommands(parent *cobra.Command) {
|
||||||
packagesCmd := parent.NewSubCommand("packages", "Manage local PHP packages")
|
packagesCmd := &cobra.Command{
|
||||||
packagesCmd.LongDescription("Link and manage local PHP packages for development.\n\n" +
|
Use: "packages",
|
||||||
|
Short: "Manage local PHP packages",
|
||||||
|
Long: "Link and manage local PHP packages for development.\n\n" +
|
||||||
"Similar to npm link, this adds path repositories to composer.json\n" +
|
"Similar to npm link, this adds path repositories to composer.json\n" +
|
||||||
"for developing packages alongside your project.\n\n" +
|
"for developing packages alongside your project.\n\n" +
|
||||||
"Commands:\n" +
|
"Commands:\n" +
|
||||||
" link - Link local packages by path\n" +
|
" link - Link local packages by path\n" +
|
||||||
" unlink - Unlink packages by name\n" +
|
" unlink - Unlink packages by name\n" +
|
||||||
" update - Update linked packages\n" +
|
" update - Update linked packages\n" +
|
||||||
" list - List linked packages")
|
" list - List linked packages",
|
||||||
|
}
|
||||||
|
parent.AddCommand(packagesCmd)
|
||||||
|
|
||||||
addPHPPackagesLinkCommand(packagesCmd)
|
addPHPPackagesLinkCommand(packagesCmd)
|
||||||
addPHPPackagesUnlinkCommand(packagesCmd)
|
addPHPPackagesUnlinkCommand(packagesCmd)
|
||||||
|
|
@ -25,21 +29,18 @@ func addPHPPackagesCommands(parent *clir.Command) {
|
||||||
addPHPPackagesListCommand(packagesCmd)
|
addPHPPackagesListCommand(packagesCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPPackagesLinkCommand(parent *clir.Command) {
|
func addPHPPackagesLinkCommand(parent *cobra.Command) {
|
||||||
linkCmd := parent.NewSubCommand("link", "Link local packages")
|
linkCmd := &cobra.Command{
|
||||||
linkCmd.LongDescription("Link local PHP packages for development.\n\n" +
|
Use: "link [paths...]",
|
||||||
|
Short: "Link local packages",
|
||||||
|
Long: "Link local PHP packages for development.\n\n" +
|
||||||
"Adds path repositories to composer.json with symlink enabled.\n" +
|
"Adds path repositories to composer.json with symlink enabled.\n" +
|
||||||
"The package name is auto-detected from each path's composer.json.\n\n" +
|
"The package name is auto-detected from each path's composer.json.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php packages link ../my-package\n" +
|
" core php packages link ../my-package\n" +
|
||||||
" core php packages link ../pkg-a ../pkg-b")
|
" core php packages link ../pkg-a ../pkg-b",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
linkCmd.Action(func() error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
args := linkCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("at least one package path is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -53,23 +54,23 @@ func addPHPPackagesLinkCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s Packages linked. Run 'composer update' to install.\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Packages linked. Run 'composer update' to install.\n", successStyle.Render("Done:"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPPackagesUnlinkCommand(parent *clir.Command) {
|
parent.AddCommand(linkCmd)
|
||||||
unlinkCmd := parent.NewSubCommand("unlink", "Unlink packages")
|
}
|
||||||
unlinkCmd.LongDescription("Remove linked packages from composer.json.\n\n" +
|
|
||||||
|
func addPHPPackagesUnlinkCommand(parent *cobra.Command) {
|
||||||
|
unlinkCmd := &cobra.Command{
|
||||||
|
Use: "unlink [packages...]",
|
||||||
|
Short: "Unlink packages",
|
||||||
|
Long: "Remove linked packages from composer.json.\n\n" +
|
||||||
"Removes path repositories by package name.\n\n" +
|
"Removes path repositories by package name.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php packages unlink vendor/my-package\n" +
|
" core php packages unlink vendor/my-package\n" +
|
||||||
" core php packages unlink vendor/pkg-a vendor/pkg-b")
|
" core php packages unlink vendor/pkg-a vendor/pkg-b",
|
||||||
|
Args: cobra.MinimumNArgs(1),
|
||||||
unlinkCmd.Action(func() error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
args := unlinkCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
|
||||||
return fmt.Errorf("at least one package name is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -83,25 +84,27 @@ func addPHPPackagesUnlinkCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s Packages unlinked. Run 'composer update' to remove.\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Packages unlinked. Run 'composer update' to remove.\n", successStyle.Render("Done:"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPPackagesUpdateCommand(parent *clir.Command) {
|
parent.AddCommand(unlinkCmd)
|
||||||
updateCmd := parent.NewSubCommand("update", "Update linked packages")
|
}
|
||||||
updateCmd.LongDescription("Run composer update for linked packages.\n\n" +
|
|
||||||
|
func addPHPPackagesUpdateCommand(parent *cobra.Command) {
|
||||||
|
updateCmd := &cobra.Command{
|
||||||
|
Use: "update [packages...]",
|
||||||
|
Short: "Update linked packages",
|
||||||
|
Long: "Run composer update for linked packages.\n\n" +
|
||||||
"If no packages specified, updates all packages.\n\n" +
|
"If no packages specified, updates all packages.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php packages update\n" +
|
" core php packages update\n" +
|
||||||
" core php packages update vendor/my-package")
|
" core php packages update vendor/my-package",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
updateCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
args := updateCmd.OtherArgs()
|
|
||||||
|
|
||||||
fmt.Printf("%s Updating packages...\n\n", dimStyle.Render("PHP:"))
|
fmt.Printf("%s Updating packages...\n\n", dimStyle.Render("PHP:"))
|
||||||
|
|
||||||
if err := phppkg.UpdatePackages(cwd, args); err != nil {
|
if err := phppkg.UpdatePackages(cwd, args); err != nil {
|
||||||
|
|
@ -110,15 +113,19 @@ func addPHPPackagesUpdateCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s Packages updated\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Packages updated\n", successStyle.Render("Done:"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPPackagesListCommand(parent *clir.Command) {
|
parent.AddCommand(updateCmd)
|
||||||
listCmd := parent.NewSubCommand("list", "List linked packages")
|
}
|
||||||
listCmd.LongDescription("List all locally linked packages.\n\n" +
|
|
||||||
"Shows package name, path, and version for each linked package.")
|
|
||||||
|
|
||||||
listCmd.Action(func() error {
|
func addPHPPackagesListCommand(parent *cobra.Command) {
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List linked packages",
|
||||||
|
Long: "List all locally linked packages.\n\n" +
|
||||||
|
"Shows package name, path, and version for each linked package.",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -153,5 +160,8 @@ func addPHPPackagesListCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(listCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,32 +11,28 @@ import (
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
phppkg "github.com/host-uk/core/pkg/php"
|
phppkg "github.com/host-uk/core/pkg/php"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
func addPHPTestCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
parallel bool
|
testParallel bool
|
||||||
coverage bool
|
testCoverage bool
|
||||||
filter string
|
testFilter string
|
||||||
group string
|
testGroup string
|
||||||
)
|
)
|
||||||
|
|
||||||
testCmd := parent.NewSubCommand("test", "Run PHP tests (PHPUnit/Pest)")
|
func addPHPTestCommand(parent *cobra.Command) {
|
||||||
testCmd.LongDescription("Run PHP tests using PHPUnit or Pest.\n\n" +
|
testCmd := &cobra.Command{
|
||||||
|
Use: "test",
|
||||||
|
Short: "Run PHP tests (PHPUnit/Pest)",
|
||||||
|
Long: "Run PHP tests using PHPUnit or Pest.\n\n" +
|
||||||
"Auto-detects Pest if tests/Pest.php exists, otherwise uses PHPUnit.\n\n" +
|
"Auto-detects Pest if tests/Pest.php exists, otherwise uses PHPUnit.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php test # Run all tests\n" +
|
" core php test # Run all tests\n" +
|
||||||
" core php test --parallel # Run tests in parallel\n" +
|
" core php test --parallel # Run tests in parallel\n" +
|
||||||
" core php test --coverage # Run with coverage\n" +
|
" core php test --coverage # Run with coverage\n" +
|
||||||
" core php test --filter UserTest # Filter by test name")
|
" core php test --filter UserTest # Filter by test name",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
testCmd.BoolFlag("parallel", "Run tests in parallel", ¶llel)
|
|
||||||
testCmd.BoolFlag("coverage", "Generate code coverage", &coverage)
|
|
||||||
testCmd.StringFlag("filter", "Filter tests by name pattern", &filter)
|
|
||||||
testCmd.StringFlag("group", "Run only tests in specified group", &group)
|
|
||||||
|
|
||||||
testCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -54,14 +50,14 @@ func addPHPTestCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := phppkg.TestOptions{
|
opts := phppkg.TestOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Filter: filter,
|
Filter: testFilter,
|
||||||
Parallel: parallel,
|
Parallel: testParallel,
|
||||||
Coverage: coverage,
|
Coverage: testCoverage,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
if group != "" {
|
if testGroup != "" {
|
||||||
opts.Groups = []string{group}
|
opts.Groups = []string{testGroup}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := phppkg.RunTests(ctx, opts); err != nil {
|
if err := phppkg.RunTests(ctx, opts); err != nil {
|
||||||
|
|
@ -69,26 +65,32 @@ func addPHPTestCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
testCmd.Flags().BoolVar(&testParallel, "parallel", false, "Run tests in parallel")
|
||||||
|
testCmd.Flags().BoolVar(&testCoverage, "coverage", false, "Generate code coverage")
|
||||||
|
testCmd.Flags().StringVar(&testFilter, "filter", "", "Filter tests by name pattern")
|
||||||
|
testCmd.Flags().StringVar(&testGroup, "group", "", "Run only tests in specified group")
|
||||||
|
|
||||||
|
parent.AddCommand(testCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPFmtCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
fix bool
|
fmtFix bool
|
||||||
diff bool
|
fmtDiff bool
|
||||||
)
|
)
|
||||||
|
|
||||||
fmtCmd := parent.NewSubCommand("fmt", "Format PHP code with Laravel Pint")
|
func addPHPFmtCommand(parent *cobra.Command) {
|
||||||
fmtCmd.LongDescription("Format PHP code using Laravel Pint.\n\n" +
|
fmtCmd := &cobra.Command{
|
||||||
|
Use: "fmt [paths...]",
|
||||||
|
Short: "Format PHP code with Laravel Pint",
|
||||||
|
Long: "Format PHP code using Laravel Pint.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php fmt # Check formatting (dry-run)\n" +
|
" core php fmt # Check formatting (dry-run)\n" +
|
||||||
" core php fmt --fix # Auto-fix formatting issues\n" +
|
" core php fmt --fix # Auto-fix formatting issues\n" +
|
||||||
" core php fmt --diff # Show diff of changes")
|
" core php fmt --diff # Show diff of changes",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
fmtCmd.BoolFlag("fix", "Auto-fix formatting issues", &fix)
|
|
||||||
fmtCmd.BoolFlag("diff", "Show diff of changes", &diff)
|
|
||||||
|
|
||||||
fmtCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -105,7 +107,7 @@ func addPHPFmtCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
action := "Checking"
|
action := "Checking"
|
||||||
if fix {
|
if fmtFix {
|
||||||
action = "Formatting"
|
action = "Formatting"
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s code with %s\n\n", dimStyle.Render("PHP:"), action, formatter)
|
fmt.Printf("%s %s code with %s\n\n", dimStyle.Render("PHP:"), action, formatter)
|
||||||
|
|
@ -114,51 +116,55 @@ func addPHPFmtCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := phppkg.FormatOptions{
|
opts := phppkg.FormatOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Fix: fix,
|
Fix: fmtFix,
|
||||||
Diff: diff,
|
Diff: fmtDiff,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get any additional paths from args
|
// Get any additional paths from args
|
||||||
if args := fmtCmd.OtherArgs(); len(args) > 0 {
|
if len(args) > 0 {
|
||||||
opts.Paths = args
|
opts.Paths = args
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := phppkg.Format(ctx, opts); err != nil {
|
if err := phppkg.Format(ctx, opts); err != nil {
|
||||||
if fix {
|
if fmtFix {
|
||||||
return fmt.Errorf("formatting failed: %w", err)
|
return fmt.Errorf("formatting failed: %w", err)
|
||||||
}
|
}
|
||||||
return fmt.Errorf("formatting issues found: %w", err)
|
return fmt.Errorf("formatting issues found: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if fix {
|
if fmtFix {
|
||||||
fmt.Printf("\n%s Code formatted successfully\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Code formatted successfully\n", successStyle.Render("Done:"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s No formatting issues found\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s No formatting issues found\n", successStyle.Render("Done:"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
fmtCmd.Flags().BoolVar(&fmtFix, "fix", false, "Auto-fix formatting issues")
|
||||||
|
fmtCmd.Flags().BoolVar(&fmtDiff, "diff", false, "Show diff of changes")
|
||||||
|
|
||||||
|
parent.AddCommand(fmtCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPAnalyseCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
level int
|
analyseLevel int
|
||||||
memory string
|
analyseMemory string
|
||||||
)
|
)
|
||||||
|
|
||||||
analyseCmd := parent.NewSubCommand("analyse", "Run PHPStan static analysis")
|
func addPHPAnalyseCommand(parent *cobra.Command) {
|
||||||
analyseCmd.LongDescription("Run PHPStan or Larastan static analysis.\n\n" +
|
analyseCmd := &cobra.Command{
|
||||||
|
Use: "analyse [paths...]",
|
||||||
|
Short: "Run PHPStan static analysis",
|
||||||
|
Long: "Run PHPStan or Larastan static analysis.\n\n" +
|
||||||
"Auto-detects Larastan if installed, otherwise uses PHPStan.\n\n" +
|
"Auto-detects Larastan if installed, otherwise uses PHPStan.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php analyse # Run analysis\n" +
|
" core php analyse # Run analysis\n" +
|
||||||
" core php analyse --level 9 # Run at max strictness\n" +
|
" core php analyse --level 9 # Run at max strictness\n" +
|
||||||
" core php analyse --memory 2G # Increase memory limit")
|
" core php analyse --memory 2G # Increase memory limit",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
analyseCmd.IntFlag("level", "PHPStan analysis level (0-9)", &level)
|
|
||||||
analyseCmd.StringFlag("memory", "Memory limit (e.g., 2G)", &memory)
|
|
||||||
|
|
||||||
analyseCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -180,13 +186,13 @@ func addPHPAnalyseCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := phppkg.AnalyseOptions{
|
opts := phppkg.AnalyseOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Level: level,
|
Level: analyseLevel,
|
||||||
Memory: memory,
|
Memory: analyseMemory,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get any additional paths from args
|
// Get any additional paths from args
|
||||||
if args := analyseCmd.OtherArgs(); len(args) > 0 {
|
if len(args) > 0 {
|
||||||
opts.Paths = args
|
opts.Paths = args
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,37 +202,39 @@ func addPHPAnalyseCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s No issues found\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s No issues found\n", successStyle.Render("Done:"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
analyseCmd.Flags().IntVar(&analyseLevel, "level", 0, "PHPStan analysis level (0-9)")
|
||||||
|
analyseCmd.Flags().StringVar(&analyseMemory, "memory", "", "Memory limit (e.g., 2G)")
|
||||||
|
|
||||||
|
parent.AddCommand(analyseCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// New QA Commands
|
// New QA Commands
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
func addPHPPsalmCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
level int
|
psalmLevel int
|
||||||
fix bool
|
psalmFix bool
|
||||||
baseline bool
|
psalmBaseline bool
|
||||||
showInfo bool
|
psalmShowInfo bool
|
||||||
)
|
)
|
||||||
|
|
||||||
psalmCmd := parent.NewSubCommand("psalm", "Run Psalm static analysis")
|
func addPHPPsalmCommand(parent *cobra.Command) {
|
||||||
psalmCmd.LongDescription("Run Psalm deep static analysis with Laravel plugin support.\n\n" +
|
psalmCmd := &cobra.Command{
|
||||||
|
Use: "psalm",
|
||||||
|
Short: "Run Psalm static analysis",
|
||||||
|
Long: "Run Psalm deep static analysis with Laravel plugin support.\n\n" +
|
||||||
"Psalm provides deeper type inference than PHPStan and catches\n" +
|
"Psalm provides deeper type inference than PHPStan and catches\n" +
|
||||||
"different classes of bugs. Both should be run for best coverage.\n\n" +
|
"different classes of bugs. Both should be run for best coverage.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php psalm # Run analysis\n" +
|
" core php psalm # Run analysis\n" +
|
||||||
" core php psalm --fix # Auto-fix issues where possible\n" +
|
" core php psalm --fix # Auto-fix issues where possible\n" +
|
||||||
" core php psalm --level 3 # Run at specific level (1-8)\n" +
|
" core php psalm --level 3 # Run at specific level (1-8)\n" +
|
||||||
" core php psalm --baseline # Generate baseline file")
|
" core php psalm --baseline # Generate baseline file",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
psalmCmd.IntFlag("level", "Error level (1=strictest, 8=most lenient)", &level)
|
|
||||||
psalmCmd.BoolFlag("fix", "Auto-fix issues where possible", &fix)
|
|
||||||
psalmCmd.BoolFlag("baseline", "Generate/update baseline file", &baseline)
|
|
||||||
psalmCmd.BoolFlag("show-info", "Show info-level issues", &showInfo)
|
|
||||||
|
|
||||||
psalmCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -246,7 +254,7 @@ func addPHPPsalmCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
action := "Analysing"
|
action := "Analysing"
|
||||||
if fix {
|
if psalmFix {
|
||||||
action = "Analysing and fixing"
|
action = "Analysing and fixing"
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s code with Psalm\n\n", dimStyle.Render("Psalm:"), action)
|
fmt.Printf("%s %s code with Psalm\n\n", dimStyle.Render("Psalm:"), action)
|
||||||
|
|
@ -255,10 +263,10 @@ func addPHPPsalmCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := phppkg.PsalmOptions{
|
opts := phppkg.PsalmOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Level: level,
|
Level: psalmLevel,
|
||||||
Fix: fix,
|
Fix: psalmFix,
|
||||||
Baseline: baseline,
|
Baseline: psalmBaseline,
|
||||||
ShowInfo: showInfo,
|
ShowInfo: psalmShowInfo,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -268,27 +276,33 @@ func addPHPPsalmCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s No issues found\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s No issues found\n", successStyle.Render("Done:"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
psalmCmd.Flags().IntVar(&psalmLevel, "level", 0, "Error level (1=strictest, 8=most lenient)")
|
||||||
|
psalmCmd.Flags().BoolVar(&psalmFix, "fix", false, "Auto-fix issues where possible")
|
||||||
|
psalmCmd.Flags().BoolVar(&psalmBaseline, "baseline", false, "Generate/update baseline file")
|
||||||
|
psalmCmd.Flags().BoolVar(&psalmShowInfo, "show-info", false, "Show info-level issues")
|
||||||
|
|
||||||
|
parent.AddCommand(psalmCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPAuditCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
jsonOutput bool
|
auditJSONOutput bool
|
||||||
fix bool
|
auditFix bool
|
||||||
)
|
)
|
||||||
|
|
||||||
auditCmd := parent.NewSubCommand("audit", "Security audit for dependencies")
|
func addPHPAuditCommand(parent *cobra.Command) {
|
||||||
auditCmd.LongDescription("Check PHP and JavaScript dependencies for known vulnerabilities.\n\n" +
|
auditCmd := &cobra.Command{
|
||||||
|
Use: "audit",
|
||||||
|
Short: "Security audit for dependencies",
|
||||||
|
Long: "Check PHP and JavaScript dependencies for known vulnerabilities.\n\n" +
|
||||||
"Runs composer audit and npm audit (if package.json exists).\n\n" +
|
"Runs composer audit and npm audit (if package.json exists).\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php audit # Check all dependencies\n" +
|
" core php audit # Check all dependencies\n" +
|
||||||
" core php audit --json # Output as JSON\n" +
|
" core php audit --json # Output as JSON\n" +
|
||||||
" core php audit --fix # Auto-fix where possible (npm only)")
|
" core php audit --fix # Auto-fix where possible (npm only)",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
auditCmd.BoolFlag("json", "Output in JSON format", &jsonOutput)
|
|
||||||
auditCmd.BoolFlag("fix", "Auto-fix vulnerabilities (npm only)", &fix)
|
|
||||||
|
|
||||||
auditCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -304,8 +318,8 @@ func addPHPAuditCommand(parent *clir.Command) {
|
||||||
|
|
||||||
results, err := phppkg.RunAudit(ctx, phppkg.AuditOptions{
|
results, err := phppkg.RunAudit(ctx, phppkg.AuditOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
JSON: jsonOutput,
|
JSON: auditJSONOutput,
|
||||||
Fix: fix,
|
Fix: auditFix,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -360,32 +374,34 @@ func addPHPAuditCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("%s All dependencies are secure\n", successStyle.Render("Done:"))
|
fmt.Printf("%s All dependencies are secure\n", successStyle.Render("Done:"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
auditCmd.Flags().BoolVar(&auditJSONOutput, "json", false, "Output in JSON format")
|
||||||
|
auditCmd.Flags().BoolVar(&auditFix, "fix", false, "Auto-fix vulnerabilities (npm only)")
|
||||||
|
|
||||||
|
parent.AddCommand(auditCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPSecurityCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
severity string
|
securitySeverity string
|
||||||
jsonOutput bool
|
securityJSONOutput bool
|
||||||
sarif bool
|
securitySarif bool
|
||||||
url string
|
securityURL string
|
||||||
)
|
)
|
||||||
|
|
||||||
securityCmd := parent.NewSubCommand("security", "Security vulnerability scanning")
|
func addPHPSecurityCommand(parent *cobra.Command) {
|
||||||
securityCmd.LongDescription("Scan for security vulnerabilities in configuration and code.\n\n" +
|
securityCmd := &cobra.Command{
|
||||||
|
Use: "security",
|
||||||
|
Short: "Security vulnerability scanning",
|
||||||
|
Long: "Scan for security vulnerabilities in configuration and code.\n\n" +
|
||||||
"Checks environment config, file permissions, code patterns,\n" +
|
"Checks environment config, file permissions, code patterns,\n" +
|
||||||
"and runs security-focused static analysis.\n\n" +
|
"and runs security-focused static analysis.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php security # Run all checks\n" +
|
" core php security # Run all checks\n" +
|
||||||
" core php security --severity=high # Only high+ severity\n" +
|
" core php security --severity=high # Only high+ severity\n" +
|
||||||
" core php security --json # JSON output")
|
" core php security --json # JSON output",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
securityCmd.StringFlag("severity", "Minimum severity (critical, high, medium, low)", &severity)
|
|
||||||
securityCmd.BoolFlag("json", "Output in JSON format", &jsonOutput)
|
|
||||||
securityCmd.BoolFlag("sarif", "Output in SARIF format (for GitHub Security)", &sarif)
|
|
||||||
securityCmd.StringFlag("url", "URL to check HTTP headers (optional)", &url)
|
|
||||||
|
|
||||||
securityCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -401,10 +417,10 @@ func addPHPSecurityCommand(parent *clir.Command) {
|
||||||
|
|
||||||
result, err := phppkg.RunSecurityChecks(ctx, phppkg.SecurityOptions{
|
result, err := phppkg.RunSecurityChecks(ctx, phppkg.SecurityOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Severity: severity,
|
Severity: securitySeverity,
|
||||||
JSON: jsonOutput,
|
JSON: securityJSONOutput,
|
||||||
SARIF: sarif,
|
SARIF: securitySarif,
|
||||||
URL: url,
|
URL: securityURL,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -461,18 +477,28 @@ func addPHPSecurityCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
securityCmd.Flags().StringVar(&securitySeverity, "severity", "", "Minimum severity (critical, high, medium, low)")
|
||||||
|
securityCmd.Flags().BoolVar(&securityJSONOutput, "json", false, "Output in JSON format")
|
||||||
|
securityCmd.Flags().BoolVar(&securitySarif, "sarif", false, "Output in SARIF format (for GitHub Security)")
|
||||||
|
securityCmd.Flags().StringVar(&securityURL, "url", "", "URL to check HTTP headers (optional)")
|
||||||
|
|
||||||
|
parent.AddCommand(securityCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPQACommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
quick bool
|
qaQuick bool
|
||||||
full bool
|
qaFull bool
|
||||||
fix bool
|
qaFix bool
|
||||||
)
|
)
|
||||||
|
|
||||||
qaCmd := parent.NewSubCommand("qa", "Run full QA pipeline")
|
func addPHPQACommand(parent *cobra.Command) {
|
||||||
qaCmd.LongDescription("Run the complete quality assurance pipeline.\n\n" +
|
qaCmd := &cobra.Command{
|
||||||
|
Use: "qa",
|
||||||
|
Short: "Run full QA pipeline",
|
||||||
|
Long: "Run the complete quality assurance pipeline.\n\n" +
|
||||||
"Stages:\n" +
|
"Stages:\n" +
|
||||||
" quick: Security audit, code style, PHPStan\n" +
|
" quick: Security audit, code style, PHPStan\n" +
|
||||||
" standard: Psalm, tests\n" +
|
" standard: Psalm, tests\n" +
|
||||||
|
|
@ -481,13 +507,8 @@ func addPHPQACommand(parent *clir.Command) {
|
||||||
" core php qa # Run quick + standard stages\n" +
|
" core php qa # Run quick + standard stages\n" +
|
||||||
" core php qa --quick # Only quick checks\n" +
|
" core php qa --quick # Only quick checks\n" +
|
||||||
" core php qa --full # All stages including slow ones\n" +
|
" core php qa --full # All stages including slow ones\n" +
|
||||||
" core php qa --fix # Auto-fix where possible")
|
" core php qa --fix # Auto-fix where possible",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
qaCmd.BoolFlag("quick", "Only run quick checks", &quick)
|
|
||||||
qaCmd.BoolFlag("full", "Run all stages including slow checks", &full)
|
|
||||||
qaCmd.BoolFlag("fix", "Auto-fix issues where possible", &fix)
|
|
||||||
|
|
||||||
qaCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -500,9 +521,9 @@ func addPHPQACommand(parent *clir.Command) {
|
||||||
// Determine stages
|
// Determine stages
|
||||||
opts := phppkg.QAOptions{
|
opts := phppkg.QAOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Quick: quick,
|
Quick: qaQuick,
|
||||||
Full: full,
|
Full: qaFull,
|
||||||
Fix: fix,
|
Fix: qaFix,
|
||||||
}
|
}
|
||||||
stages := phppkg.GetQAStages(opts)
|
stages := phppkg.GetQAStages(opts)
|
||||||
|
|
||||||
|
|
@ -527,7 +548,7 @@ func addPHPQACommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, checkName := range checks {
|
for _, checkName := range checks {
|
||||||
result := runQACheck(ctx, cwd, checkName, fix)
|
result := runQACheck(ctx, cwd, checkName, qaFix)
|
||||||
result.Stage = stage
|
result.Stage = stage
|
||||||
results = append(results, result)
|
results = append(results, result)
|
||||||
|
|
||||||
|
|
@ -565,7 +586,7 @@ func addPHPQACommand(parent *clir.Command) {
|
||||||
// Show what needs fixing
|
// Show what needs fixing
|
||||||
fmt.Printf("%s\n", dimStyle.Render("To fix:"))
|
fmt.Printf("%s\n", dimStyle.Render("To fix:"))
|
||||||
for _, check := range failedChecks {
|
for _, check := range failedChecks {
|
||||||
fixCmd := getQAFixCommand(check.Name, fix)
|
fixCmd := getQAFixCommand(check.Name, qaFix)
|
||||||
issue := check.Output
|
issue := check.Output
|
||||||
if issue == "" {
|
if issue == "" {
|
||||||
issue = "issues found"
|
issue = "issues found"
|
||||||
|
|
@ -577,7 +598,14 @@ func addPHPQACommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("QA pipeline failed")
|
return fmt.Errorf("QA pipeline failed")
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
qaCmd.Flags().BoolVar(&qaQuick, "quick", false, "Only run quick checks")
|
||||||
|
qaCmd.Flags().BoolVar(&qaFull, "full", false, "Run all stages including slow checks")
|
||||||
|
qaCmd.Flags().BoolVar(&qaFix, "fix", false, "Auto-fix issues where possible")
|
||||||
|
|
||||||
|
parent.AddCommand(qaCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getQAFixCommand(checkName string, fixEnabled bool) string {
|
func getQAFixCommand(checkName string, fixEnabled bool) string {
|
||||||
|
|
@ -677,27 +705,24 @@ func runQACheck(ctx context.Context, dir string, checkName string, fix bool) php
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPRectorCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
fix bool
|
rectorFix bool
|
||||||
diff bool
|
rectorDiff bool
|
||||||
clearCache bool
|
rectorClearCache bool
|
||||||
)
|
)
|
||||||
|
|
||||||
rectorCmd := parent.NewSubCommand("rector", "Automated code refactoring")
|
func addPHPRectorCommand(parent *cobra.Command) {
|
||||||
rectorCmd.LongDescription("Run Rector for automated code improvements and PHP upgrades.\n\n" +
|
rectorCmd := &cobra.Command{
|
||||||
|
Use: "rector",
|
||||||
|
Short: "Automated code refactoring",
|
||||||
|
Long: "Run Rector for automated code improvements and PHP upgrades.\n\n" +
|
||||||
"Rector can automatically upgrade PHP syntax, improve code quality,\n" +
|
"Rector can automatically upgrade PHP syntax, improve code quality,\n" +
|
||||||
"and apply framework-specific refactorings.\n\n" +
|
"and apply framework-specific refactorings.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php rector # Dry-run (show changes)\n" +
|
" core php rector # Dry-run (show changes)\n" +
|
||||||
" core php rector --fix # Apply changes\n" +
|
" core php rector --fix # Apply changes\n" +
|
||||||
" core php rector --diff # Show detailed diff")
|
" core php rector --diff # Show detailed diff",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
rectorCmd.BoolFlag("fix", "Apply changes (default is dry-run)", &fix)
|
|
||||||
rectorCmd.BoolFlag("diff", "Show detailed diff of changes", &diff)
|
|
||||||
rectorCmd.BoolFlag("clear-cache", "Clear Rector cache before running", &clearCache)
|
|
||||||
|
|
||||||
rectorCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -716,7 +741,7 @@ func addPHPRectorCommand(parent *clir.Command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
action := "Analysing"
|
action := "Analysing"
|
||||||
if fix {
|
if rectorFix {
|
||||||
action = "Refactoring"
|
action = "Refactoring"
|
||||||
}
|
}
|
||||||
fmt.Printf("%s %s code with Rector\n\n", dimStyle.Render("Rector:"), action)
|
fmt.Printf("%s %s code with Rector\n\n", dimStyle.Render("Rector:"), action)
|
||||||
|
|
@ -725,14 +750,14 @@ func addPHPRectorCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := phppkg.RectorOptions{
|
opts := phppkg.RectorOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
Fix: fix,
|
Fix: rectorFix,
|
||||||
Diff: diff,
|
Diff: rectorDiff,
|
||||||
ClearCache: clearCache,
|
ClearCache: rectorClearCache,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := phppkg.RunRector(ctx, opts); err != nil {
|
if err := phppkg.RunRector(ctx, opts); err != nil {
|
||||||
if fix {
|
if rectorFix {
|
||||||
return fmt.Errorf("rector failed: %w", err)
|
return fmt.Errorf("rector failed: %w", err)
|
||||||
}
|
}
|
||||||
// Dry-run returns non-zero if changes would be made
|
// Dry-run returns non-zero if changes would be made
|
||||||
|
|
@ -740,41 +765,43 @@ func addPHPRectorCommand(parent *clir.Command) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if fix {
|
if rectorFix {
|
||||||
fmt.Printf("\n%s Code refactored successfully\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Code refactored successfully\n", successStyle.Render("Done:"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("\n%s No changes needed\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s No changes needed\n", successStyle.Render("Done:"))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rectorCmd.Flags().BoolVar(&rectorFix, "fix", false, "Apply changes (default is dry-run)")
|
||||||
|
rectorCmd.Flags().BoolVar(&rectorDiff, "diff", false, "Show detailed diff of changes")
|
||||||
|
rectorCmd.Flags().BoolVar(&rectorClearCache, "clear-cache", false, "Clear Rector cache before running")
|
||||||
|
|
||||||
|
parent.AddCommand(rectorCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPHPInfectionCommand(parent *clir.Command) {
|
|
||||||
var (
|
var (
|
||||||
minMSI int
|
infectionMinMSI int
|
||||||
minCoveredMSI int
|
infectionMinCoveredMSI int
|
||||||
threads int
|
infectionThreads int
|
||||||
filter string
|
infectionFilter string
|
||||||
onlyCovered bool
|
infectionOnlyCovered bool
|
||||||
)
|
)
|
||||||
|
|
||||||
infectionCmd := parent.NewSubCommand("infection", "Mutation testing for test quality")
|
func addPHPInfectionCommand(parent *cobra.Command) {
|
||||||
infectionCmd.LongDescription("Run Infection mutation testing to measure test suite quality.\n\n" +
|
infectionCmd := &cobra.Command{
|
||||||
|
Use: "infection",
|
||||||
|
Short: "Mutation testing for test quality",
|
||||||
|
Long: "Run Infection mutation testing to measure test suite quality.\n\n" +
|
||||||
"Mutation testing modifies your code and checks if tests catch\n" +
|
"Mutation testing modifies your code and checks if tests catch\n" +
|
||||||
"the changes. High mutation score = high quality tests.\n\n" +
|
"the changes. High mutation score = high quality tests.\n\n" +
|
||||||
"Warning: This can be slow on large codebases.\n\n" +
|
"Warning: This can be slow on large codebases.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core php infection # Run mutation testing\n" +
|
" core php infection # Run mutation testing\n" +
|
||||||
" core php infection --min-msi=70 # Require 70% mutation score\n" +
|
" core php infection --min-msi=70 # Require 70% mutation score\n" +
|
||||||
" core php infection --filter=User # Only test User* files")
|
" core php infection --filter=User # Only test User* files",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
infectionCmd.IntFlag("min-msi", "Minimum mutation score indicator (0-100, default: 50)", &minMSI)
|
|
||||||
infectionCmd.IntFlag("min-covered-msi", "Minimum covered mutation score (0-100, default: 70)", &minCoveredMSI)
|
|
||||||
infectionCmd.IntFlag("threads", "Number of parallel threads (default: 4)", &threads)
|
|
||||||
infectionCmd.StringFlag("filter", "Filter files by pattern", &filter)
|
|
||||||
infectionCmd.BoolFlag("only-covered", "Only mutate covered code", &onlyCovered)
|
|
||||||
|
|
||||||
infectionCmd.Action(func() error {
|
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get working directory: %w", err)
|
return fmt.Errorf("failed to get working directory: %w", err)
|
||||||
|
|
@ -798,11 +825,11 @@ func addPHPInfectionCommand(parent *clir.Command) {
|
||||||
|
|
||||||
opts := phppkg.InfectionOptions{
|
opts := phppkg.InfectionOptions{
|
||||||
Dir: cwd,
|
Dir: cwd,
|
||||||
MinMSI: minMSI,
|
MinMSI: infectionMinMSI,
|
||||||
MinCoveredMSI: minCoveredMSI,
|
MinCoveredMSI: infectionMinCoveredMSI,
|
||||||
Threads: threads,
|
Threads: infectionThreads,
|
||||||
Filter: filter,
|
Filter: infectionFilter,
|
||||||
OnlyCovered: onlyCovered,
|
OnlyCovered: infectionOnlyCovered,
|
||||||
Output: os.Stdout,
|
Output: os.Stdout,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -812,7 +839,16 @@ func addPHPInfectionCommand(parent *clir.Command) {
|
||||||
|
|
||||||
fmt.Printf("\n%s Mutation testing complete\n", successStyle.Render("Done:"))
|
fmt.Printf("\n%s Mutation testing complete\n", successStyle.Render("Done:"))
|
||||||
return nil
|
return nil
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
infectionCmd.Flags().IntVar(&infectionMinMSI, "min-msi", 0, "Minimum mutation score indicator (0-100, default: 50)")
|
||||||
|
infectionCmd.Flags().IntVar(&infectionMinCoveredMSI, "min-covered-msi", 0, "Minimum covered mutation score (0-100, default: 70)")
|
||||||
|
infectionCmd.Flags().IntVar(&infectionThreads, "threads", 0, "Number of parallel threads (default: 4)")
|
||||||
|
infectionCmd.Flags().StringVar(&infectionFilter, "filter", "", "Filter files by pattern")
|
||||||
|
infectionCmd.Flags().BoolVar(&infectionOnlyCovered, "only-covered", false, "Only mutate covered code")
|
||||||
|
|
||||||
|
parent.AddCommand(infectionCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSeverityStyle(severity string) lipgloss.Style {
|
func getSeverityStyle(severity string) lipgloss.Style {
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
// .core/cache/ within the workspace directory.
|
// .core/cache/ within the workspace directory.
|
||||||
package pkg
|
package pkg
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'pkg' command and all subcommands.
|
// AddCommands registers the 'pkg' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddPkgCommands(app)
|
AddPkgCommands(root)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package pkg
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style and utility aliases
|
// Style and utility aliases
|
||||||
|
|
@ -17,16 +17,20 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddPkgCommands adds the 'pkg' command and subcommands for package management.
|
// AddPkgCommands adds the 'pkg' command and subcommands for package management.
|
||||||
func AddPkgCommands(parent *clir.Cli) {
|
func AddPkgCommands(root *cobra.Command) {
|
||||||
pkgCmd := parent.NewSubCommand("pkg", "Package management for core-* repos")
|
pkgCmd := &cobra.Command{
|
||||||
pkgCmd.LongDescription("Manage host-uk/core-* packages and repositories.\n\n" +
|
Use: "pkg",
|
||||||
|
Short: "Package management for core-* repos",
|
||||||
|
Long: "Manage host-uk/core-* packages and repositories.\n\n" +
|
||||||
"Commands:\n" +
|
"Commands:\n" +
|
||||||
" search Search GitHub for packages\n" +
|
" search Search GitHub for packages\n" +
|
||||||
" install Clone a package from GitHub\n" +
|
" install Clone a package from GitHub\n" +
|
||||||
" list List installed packages\n" +
|
" list List installed packages\n" +
|
||||||
" update Update installed packages\n" +
|
" update Update installed packages\n" +
|
||||||
" outdated Check for outdated packages")
|
" outdated Check for outdated packages",
|
||||||
|
}
|
||||||
|
|
||||||
|
root.AddCommand(pkgCmd)
|
||||||
addPkgSearchCommand(pkgCmd)
|
addPkgSearchCommand(pkgCmd)
|
||||||
addPkgInstallCommand(pkgCmd)
|
addPkgInstallCommand(pkgCmd)
|
||||||
addPkgListCommand(pkgCmd)
|
addPkgListCommand(pkgCmd)
|
||||||
|
|
|
||||||
|
|
@ -8,31 +8,36 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
installTargetDir string
|
||||||
|
installAddToReg bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// addPkgInstallCommand adds the 'pkg install' command.
|
// addPkgInstallCommand adds the 'pkg install' command.
|
||||||
func addPkgInstallCommand(parent *clir.Command) {
|
func addPkgInstallCommand(parent *cobra.Command) {
|
||||||
var targetDir string
|
installCmd := &cobra.Command{
|
||||||
var addToRegistry bool
|
Use: "install <org/repo>",
|
||||||
|
Short: "Clone a package from GitHub",
|
||||||
installCmd := parent.NewSubCommand("install", "Clone a package from GitHub")
|
Long: "Clones a repository from GitHub.\n\n" +
|
||||||
installCmd.LongDescription("Clones a repository from GitHub.\n\n" +
|
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core pkg install host-uk/core-php\n" +
|
" core pkg install host-uk/core-php\n" +
|
||||||
" core pkg install host-uk/core-tenant --dir ./packages\n" +
|
" core pkg install host-uk/core-tenant --dir ./packages\n" +
|
||||||
" core pkg install host-uk/core-admin --add")
|
" core pkg install host-uk/core-admin --add",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
installCmd.StringFlag("dir", "Target directory (default: ./packages or current dir)", &targetDir)
|
|
||||||
installCmd.BoolFlag("add", "Add to repos.yaml registry", &addToRegistry)
|
|
||||||
|
|
||||||
installCmd.Action(func() error {
|
|
||||||
args := installCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("repository is required (e.g., core pkg install host-uk/core-php)")
|
return fmt.Errorf("repository is required (e.g., core pkg install host-uk/core-php)")
|
||||||
}
|
}
|
||||||
return runPkgInstall(args[0], targetDir, addToRegistry)
|
return runPkgInstall(args[0], installTargetDir, installAddToReg)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
installCmd.Flags().StringVar(&installTargetDir, "dir", "", "Target directory (default: ./packages or current dir)")
|
||||||
|
installCmd.Flags().BoolVar(&installAddToReg, "add", false, "Add to repos.yaml registry")
|
||||||
|
|
||||||
|
parent.AddCommand(installCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error {
|
func runPkgInstall(repoArg, targetDir string, addToRegistry bool) error {
|
||||||
|
|
|
||||||
|
|
@ -8,20 +8,24 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addPkgListCommand adds the 'pkg list' command.
|
// addPkgListCommand adds the 'pkg list' command.
|
||||||
func addPkgListCommand(parent *clir.Command) {
|
func addPkgListCommand(parent *cobra.Command) {
|
||||||
listCmd := parent.NewSubCommand("list", "List installed packages")
|
listCmd := &cobra.Command{
|
||||||
listCmd.LongDescription("Lists all packages in the current workspace.\n\n" +
|
Use: "list",
|
||||||
|
Short: "List installed packages",
|
||||||
|
Long: "Lists all packages in the current workspace.\n\n" +
|
||||||
"Reads from repos.yaml or scans for git repositories.\n\n" +
|
"Reads from repos.yaml or scans for git repositories.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core pkg list")
|
" core pkg list",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
listCmd.Action(func() error {
|
|
||||||
return runPkgList()
|
return runPkgList()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(listCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPkgList() error {
|
func runPkgList() error {
|
||||||
|
|
@ -89,25 +93,28 @@ func runPkgList() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addPkgUpdateCommand adds the 'pkg update' command.
|
var updateAll bool
|
||||||
func addPkgUpdateCommand(parent *clir.Command) {
|
|
||||||
var all bool
|
|
||||||
|
|
||||||
updateCmd := parent.NewSubCommand("update", "Update installed packages")
|
// addPkgUpdateCommand adds the 'pkg update' command.
|
||||||
updateCmd.LongDescription("Pulls latest changes for installed packages.\n\n" +
|
func addPkgUpdateCommand(parent *cobra.Command) {
|
||||||
|
updateCmd := &cobra.Command{
|
||||||
|
Use: "update [packages...]",
|
||||||
|
Short: "Update installed packages",
|
||||||
|
Long: "Pulls latest changes for installed packages.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core pkg update core-php # Update specific package\n" +
|
" core pkg update core-php # Update specific package\n" +
|
||||||
" core pkg update --all # Update all packages")
|
" core pkg update --all # Update all packages",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
updateCmd.BoolFlag("all", "Update all packages", &all)
|
if !updateAll && len(args) == 0 {
|
||||||
|
|
||||||
updateCmd.Action(func() error {
|
|
||||||
args := updateCmd.OtherArgs()
|
|
||||||
if !all && len(args) == 0 {
|
|
||||||
return fmt.Errorf("specify package name or use --all")
|
return fmt.Errorf("specify package name or use --all")
|
||||||
}
|
}
|
||||||
return runPkgUpdate(args, all)
|
return runPkgUpdate(args, updateAll)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCmd.Flags().BoolVar(&updateAll, "all", false, "Update all packages")
|
||||||
|
|
||||||
|
parent.AddCommand(updateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPkgUpdate(packages []string, all bool) error {
|
func runPkgUpdate(packages []string, all bool) error {
|
||||||
|
|
@ -177,15 +184,19 @@ func runPkgUpdate(packages []string, all bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addPkgOutdatedCommand adds the 'pkg outdated' command.
|
// addPkgOutdatedCommand adds the 'pkg outdated' command.
|
||||||
func addPkgOutdatedCommand(parent *clir.Command) {
|
func addPkgOutdatedCommand(parent *cobra.Command) {
|
||||||
outdatedCmd := parent.NewSubCommand("outdated", "Check for outdated packages")
|
outdatedCmd := &cobra.Command{
|
||||||
outdatedCmd.LongDescription("Checks which packages have unpulled commits.\n\n" +
|
Use: "outdated",
|
||||||
|
Short: "Check for outdated packages",
|
||||||
|
Long: "Checks which packages have unpulled commits.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core pkg outdated")
|
" core pkg outdated",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
outdatedCmd.Action(func() error {
|
|
||||||
return runPkgOutdated()
|
return runPkgOutdated()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(outdatedCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runPkgOutdated() error {
|
func runPkgOutdated() error {
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,33 @@ import (
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/cache"
|
"github.com/host-uk/core/pkg/cache"
|
||||||
"github.com/host-uk/core/pkg/repos"
|
"github.com/host-uk/core/pkg/repos"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
searchOrg string
|
||||||
|
searchPattern string
|
||||||
|
searchType string
|
||||||
|
searchLimit int
|
||||||
|
searchRefresh bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// addPkgSearchCommand adds the 'pkg search' command.
|
// addPkgSearchCommand adds the 'pkg search' command.
|
||||||
func addPkgSearchCommand(parent *clir.Command) {
|
func addPkgSearchCommand(parent *cobra.Command) {
|
||||||
var org string
|
searchCmd := &cobra.Command{
|
||||||
var pattern string
|
Use: "search",
|
||||||
var repoType string
|
Short: "Search GitHub for packages",
|
||||||
var limit int
|
Long: "Searches GitHub for repositories matching a pattern.\n" +
|
||||||
var refresh bool
|
|
||||||
|
|
||||||
searchCmd := parent.NewSubCommand("search", "Search GitHub for packages")
|
|
||||||
searchCmd.LongDescription("Searches GitHub for repositories matching a pattern.\n" +
|
|
||||||
"Uses gh CLI for authenticated search. Results are cached for 1 hour.\n\n" +
|
"Uses gh CLI for authenticated search. Results are cached for 1 hour.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core pkg search # List all host-uk repos\n" +
|
" core pkg search # List all host-uk repos\n" +
|
||||||
" core pkg search --pattern 'core-*' # Search for core-* repos\n" +
|
" core pkg search --pattern 'core-*' # Search for core-* repos\n" +
|
||||||
" core pkg search --org mycompany # Search different org\n" +
|
" core pkg search --org mycompany # Search different org\n" +
|
||||||
" core pkg search --refresh # Bypass cache")
|
" core pkg search --refresh # Bypass cache",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
searchCmd.StringFlag("org", "GitHub organization (default: host-uk)", &org)
|
org := searchOrg
|
||||||
searchCmd.StringFlag("pattern", "Repo name pattern (* for wildcard)", &pattern)
|
pattern := searchPattern
|
||||||
searchCmd.StringFlag("type", "Filter by type in name (mod, services, plug, website)", &repoType)
|
limit := searchLimit
|
||||||
searchCmd.IntFlag("limit", "Max results (default 50)", &limit)
|
|
||||||
searchCmd.BoolFlag("refresh", "Bypass cache and fetch fresh data", &refresh)
|
|
||||||
|
|
||||||
searchCmd.Action(func() error {
|
|
||||||
if org == "" {
|
if org == "" {
|
||||||
org = "host-uk"
|
org = "host-uk"
|
||||||
}
|
}
|
||||||
|
|
@ -48,8 +48,17 @@ func addPkgSearchCommand(parent *clir.Command) {
|
||||||
if limit == 0 {
|
if limit == 0 {
|
||||||
limit = 50
|
limit = 50
|
||||||
}
|
}
|
||||||
return runPkgSearch(org, pattern, repoType, limit, refresh)
|
return runPkgSearch(org, pattern, searchType, limit, searchRefresh)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
searchCmd.Flags().StringVar(&searchOrg, "org", "", "GitHub organization (default: host-uk)")
|
||||||
|
searchCmd.Flags().StringVar(&searchPattern, "pattern", "", "Repo name pattern (* for wildcard)")
|
||||||
|
searchCmd.Flags().StringVar(&searchType, "type", "", "Filter by type in name (mod, services, plug, website)")
|
||||||
|
searchCmd.Flags().IntVar(&searchLimit, "limit", 0, "Max results (default 50)")
|
||||||
|
searchCmd.Flags().BoolVar(&searchRefresh, "refresh", false, "Bypass cache and fetch fresh data")
|
||||||
|
|
||||||
|
parent.AddCommand(searchCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ghRepo struct {
|
type ghRepo struct {
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@
|
||||||
// Configuration via .core/sdk.yaml. For SDK generation, use: core build sdk
|
// Configuration via .core/sdk.yaml. For SDK generation, use: core build sdk
|
||||||
package sdk
|
package sdk
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'sdk' command and all subcommands.
|
// AddCommands registers the 'sdk' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddSDKCommand(app)
|
root.AddCommand(sdkCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
sdkpkg "github.com/host-uk/core/pkg/sdk"
|
sdkpkg "github.com/host-uk/core/pkg/sdk"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -27,30 +27,49 @@ var (
|
||||||
Foreground(lipgloss.Color("#6b7280"))
|
Foreground(lipgloss.Color("#6b7280"))
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddSDKCommand adds the sdk command and its subcommands.
|
var sdkCmd = &cobra.Command{
|
||||||
func AddSDKCommand(app *clir.Cli) {
|
Use: "sdk",
|
||||||
sdkCmd := app.NewSubCommand("sdk", "SDK validation and API compatibility tools")
|
Short: "SDK validation and API compatibility tools",
|
||||||
sdkCmd.LongDescription("Tools for validating OpenAPI specs and checking API compatibility.\n" +
|
Long: `Tools for validating OpenAPI specs and checking API compatibility.
|
||||||
"To generate SDKs, use: core build sdk\n\n" +
|
To generate SDKs, use: core build sdk
|
||||||
"Commands:\n" +
|
|
||||||
" diff Check for breaking API changes\n" +
|
|
||||||
" validate Validate OpenAPI spec syntax")
|
|
||||||
|
|
||||||
// sdk diff
|
Commands:
|
||||||
diffCmd := sdkCmd.NewSubCommand("diff", "Check for breaking API changes")
|
diff Check for breaking API changes
|
||||||
var basePath, specPath string
|
validate Validate OpenAPI spec syntax`,
|
||||||
diffCmd.StringFlag("base", "Base spec (version tag or file)", &basePath)
|
}
|
||||||
diffCmd.StringFlag("spec", "Current spec file", &specPath)
|
|
||||||
diffCmd.Action(func() error {
|
|
||||||
return runSDKDiff(basePath, specPath)
|
|
||||||
})
|
|
||||||
|
|
||||||
// sdk validate
|
var diffBasePath string
|
||||||
validateCmd := sdkCmd.NewSubCommand("validate", "Validate OpenAPI spec")
|
var diffSpecPath string
|
||||||
validateCmd.StringFlag("spec", "Path to OpenAPI spec file", &specPath)
|
|
||||||
validateCmd.Action(func() error {
|
var sdkDiffCmd = &cobra.Command{
|
||||||
return runSDKValidate(specPath)
|
Use: "diff",
|
||||||
})
|
Short: "Check for breaking API changes",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runSDKDiff(diffBasePath, diffSpecPath)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var validateSpecPath string
|
||||||
|
|
||||||
|
var sdkValidateCmd = &cobra.Command{
|
||||||
|
Use: "validate",
|
||||||
|
Short: "Validate OpenAPI spec",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runSDKValidate(validateSpecPath)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// sdk diff flags
|
||||||
|
sdkDiffCmd.Flags().StringVar(&diffBasePath, "base", "", "Base spec (version tag or file)")
|
||||||
|
sdkDiffCmd.Flags().StringVar(&diffSpecPath, "spec", "", "Current spec file")
|
||||||
|
|
||||||
|
// sdk validate flags
|
||||||
|
sdkValidateCmd.Flags().StringVar(&validateSpecPath, "spec", "", "Path to OpenAPI spec file")
|
||||||
|
|
||||||
|
// Add subcommands
|
||||||
|
sdkCmd.AddCommand(sdkDiffCmd)
|
||||||
|
sdkCmd.AddCommand(sdkValidateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSDKDiff(basePath, specPath string) error {
|
func runSDKDiff(basePath, specPath string) error {
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,9 @@
|
||||||
// Uses gh CLI with HTTPS when authenticated, falls back to SSH.
|
// Uses gh CLI with HTTPS when authenticated, falls back to SSH.
|
||||||
package setup
|
package setup
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'setup' command and all subcommands.
|
// AddCommands registers the 'setup' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddSetupCommand(app)
|
AddSetupCommand(root)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package setup
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared package
|
// Style aliases from shared package
|
||||||
|
|
@ -21,34 +21,46 @@ const (
|
||||||
devopsReposYaml = "repos.yaml"
|
devopsReposYaml = "repos.yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddSetupCommand adds the 'setup' command to the given parent command.
|
// Setup command flags
|
||||||
func AddSetupCommand(parent *clir.Cli) {
|
var (
|
||||||
var registryPath string
|
registryPath string
|
||||||
var only string
|
only string
|
||||||
var dryRun bool
|
dryRun bool
|
||||||
var all bool
|
all bool
|
||||||
var name string
|
name string
|
||||||
var build bool
|
build bool
|
||||||
|
)
|
||||||
|
|
||||||
setupCmd := parent.NewSubCommand("setup", "Bootstrap workspace or clone packages from registry")
|
var setupCmd = &cobra.Command{
|
||||||
setupCmd.LongDescription("Sets up a development workspace.\n\n" +
|
Use: "setup",
|
||||||
"REGISTRY MODE (repos.yaml exists):\n" +
|
Short: "Bootstrap workspace or clone packages from registry",
|
||||||
" Clones all repositories defined in repos.yaml into packages/.\n" +
|
Long: `Sets up a development workspace.
|
||||||
" Skips repos that already exist. Use --only to filter by type.\n\n" +
|
|
||||||
"BOOTSTRAP MODE (no repos.yaml):\n" +
|
|
||||||
" 1. Clones core-devops to set up the workspace\n" +
|
|
||||||
" 2. Presents an interactive wizard to select packages\n" +
|
|
||||||
" 3. Clones selected packages\n\n" +
|
|
||||||
"Use --all to skip the wizard and clone everything.")
|
|
||||||
|
|
||||||
setupCmd.StringFlag("registry", "Path to repos.yaml (auto-detected if not specified)", ®istryPath)
|
REGISTRY MODE (repos.yaml exists):
|
||||||
setupCmd.StringFlag("only", "Only clone repos of these types (comma-separated: foundation,module,product)", &only)
|
Clones all repositories defined in repos.yaml into packages/.
|
||||||
setupCmd.BoolFlag("dry-run", "Show what would be cloned without cloning", &dryRun)
|
Skips repos that already exist. Use --only to filter by type.
|
||||||
setupCmd.BoolFlag("all", "Skip wizard, clone all packages (non-interactive)", &all)
|
|
||||||
setupCmd.StringFlag("name", "Project directory name for bootstrap mode", &name)
|
|
||||||
setupCmd.BoolFlag("build", "Run build after cloning", &build)
|
|
||||||
|
|
||||||
setupCmd.Action(func() error {
|
BOOTSTRAP MODE (no repos.yaml):
|
||||||
|
1. Clones core-devops to set up the workspace
|
||||||
|
2. Presents an interactive wizard to select packages
|
||||||
|
3. Clones selected packages
|
||||||
|
|
||||||
|
Use --all to skip the wizard and clone everything.`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
return runSetupOrchestrator(registryPath, only, dryRun, all, name, build)
|
return runSetupOrchestrator(registryPath, only, dryRun, all, name, build)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setupCmd.Flags().StringVar(®istryPath, "registry", "", "Path to repos.yaml (auto-detected if not specified)")
|
||||||
|
setupCmd.Flags().StringVar(&only, "only", "", "Only clone repos of these types (comma-separated: foundation,module,product)")
|
||||||
|
setupCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Show what would be cloned without cloning")
|
||||||
|
setupCmd.Flags().BoolVar(&all, "all", false, "Skip wizard, clone all packages (non-interactive)")
|
||||||
|
setupCmd.Flags().StringVar(&name, "name", "", "Project directory name for bootstrap mode")
|
||||||
|
setupCmd.Flags().BoolVar(&build, "build", false, "Run build after cloning")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSetupCommand adds the 'setup' command to the given parent command.
|
||||||
|
func AddSetupCommand(root *cobra.Command) {
|
||||||
|
root.AddCommand(setupCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,9 @@
|
||||||
// Flags: --verbose, --coverage, --short, --pkg, --run, --race, --json
|
// Flags: --verbose, --coverage, --short, --pkg, --run, --race, --json
|
||||||
package testcmd
|
package testcmd
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'test' command and all subcommands.
|
// AddCommands registers the 'test' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddTestCommand(app)
|
root.AddCommand(testCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ package testcmd
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared
|
// Style aliases from shared
|
||||||
|
|
@ -30,38 +30,44 @@ var (
|
||||||
Foreground(lipgloss.Color("#ef4444")) // red-500
|
Foreground(lipgloss.Color("#ef4444")) // red-500
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddTestCommand adds the 'test' command to the given parent command.
|
// Flag variables for test command
|
||||||
func AddTestCommand(parent *clir.Cli) {
|
var (
|
||||||
var verbose bool
|
testVerbose bool
|
||||||
var coverage bool
|
testCoverage bool
|
||||||
var short bool
|
testShort bool
|
||||||
var pkg string
|
testPkg string
|
||||||
var run string
|
testRun string
|
||||||
var race bool
|
testRace bool
|
||||||
var json bool
|
testJSON bool
|
||||||
|
)
|
||||||
|
|
||||||
testCmd := parent.NewSubCommand("test", "Run tests with coverage")
|
var testCmd = &cobra.Command{
|
||||||
testCmd.LongDescription("Runs Go tests with coverage reporting.\n\n" +
|
Use: "test",
|
||||||
"Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings on macOS.\n\n" +
|
Short: "Run tests with coverage",
|
||||||
"Examples:\n" +
|
Long: `Runs Go tests with coverage reporting.
|
||||||
" core test # Run all tests with coverage summary\n" +
|
|
||||||
" core test --verbose # Show test output as it runs\n" +
|
|
||||||
" core test --coverage # Show detailed per-package coverage\n" +
|
|
||||||
" core test --pkg ./pkg/... # Test specific packages\n" +
|
|
||||||
" core test --run TestName # Run specific test by name\n" +
|
|
||||||
" core test --short # Skip long-running tests\n" +
|
|
||||||
" core test --race # Enable race detector\n" +
|
|
||||||
" core test --json # Output JSON for CI/agents")
|
|
||||||
|
|
||||||
testCmd.BoolFlag("verbose", "Show test output as it runs (-v)", &verbose)
|
Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings on macOS.
|
||||||
testCmd.BoolFlag("coverage", "Show detailed per-package coverage", &coverage)
|
|
||||||
testCmd.BoolFlag("short", "Skip long-running tests (-short)", &short)
|
|
||||||
testCmd.StringFlag("pkg", "Package pattern to test (default: ./...)", &pkg)
|
|
||||||
testCmd.StringFlag("run", "Run only tests matching this regex (-run)", &run)
|
|
||||||
testCmd.BoolFlag("race", "Enable race detector (-race)", &race)
|
|
||||||
testCmd.BoolFlag("json", "Output JSON for CI/agents", &json)
|
|
||||||
|
|
||||||
testCmd.Action(func() error {
|
Examples:
|
||||||
return runTest(verbose, coverage, short, pkg, run, race, json)
|
core test # Run all tests with coverage summary
|
||||||
})
|
core test --verbose # Show test output as it runs
|
||||||
|
core test --coverage # Show detailed per-package coverage
|
||||||
|
core test --pkg ./pkg/... # Test specific packages
|
||||||
|
core test --run TestName # Run specific test by name
|
||||||
|
core test --short # Skip long-running tests
|
||||||
|
core test --race # Enable race detector
|
||||||
|
core test --json # Output JSON for CI/agents`,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return runTest(testVerbose, testCoverage, testShort, testPkg, testRun, testRace, testJSON)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
testCmd.Flags().BoolVar(&testVerbose, "verbose", false, "Show test output as it runs (-v)")
|
||||||
|
testCmd.Flags().BoolVar(&testCoverage, "coverage", false, "Show detailed per-package coverage")
|
||||||
|
testCmd.Flags().BoolVar(&testShort, "short", false, "Skip long-running tests (-short)")
|
||||||
|
testCmd.Flags().StringVar(&testPkg, "pkg", "", "Package pattern to test (default: ./...)")
|
||||||
|
testCmd.Flags().StringVar(&testRun, "run", "", "Run only tests matching this regex (-run)")
|
||||||
|
testCmd.Flags().BoolVar(&testRace, "race", false, "Enable race detector (-race)")
|
||||||
|
testCmd.Flags().BoolVar(&testJSON, "json", false, "Output JSON for CI/agents")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@
|
||||||
// Templates are built from YAML definitions and can include variables.
|
// Templates are built from YAML definitions and can include variables.
|
||||||
package vm
|
package vm
|
||||||
|
|
||||||
import "github.com/leaanthony/clir"
|
import "github.com/spf13/cobra"
|
||||||
|
|
||||||
// AddCommands registers the 'vm' command and all subcommands.
|
// AddCommands registers the 'vm' command and all subcommands.
|
||||||
func AddCommands(app *clir.Cli) {
|
func AddCommands(root *cobra.Command) {
|
||||||
AddVMCommands(app)
|
AddVMCommands(root)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,23 +10,25 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/container"
|
"github.com/host-uk/core/pkg/container"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
runName string
|
||||||
|
runDetach bool
|
||||||
|
runMemory int
|
||||||
|
runCPUs int
|
||||||
|
runSSHPort int
|
||||||
|
runTemplateName string
|
||||||
|
runVarFlags []string
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMRunCommand adds the 'run' command under vm.
|
// addVMRunCommand adds the 'run' command under vm.
|
||||||
func addVMRunCommand(parent *clir.Command) {
|
func addVMRunCommand(parent *cobra.Command) {
|
||||||
var (
|
runCmd := &cobra.Command{
|
||||||
name string
|
Use: "run [image]",
|
||||||
detach bool
|
Short: "Run a LinuxKit image or template",
|
||||||
memory int
|
Long: "Runs a LinuxKit image as a VM using the available hypervisor.\n\n" +
|
||||||
cpus int
|
|
||||||
sshPort int
|
|
||||||
templateName string
|
|
||||||
varFlags []string
|
|
||||||
)
|
|
||||||
|
|
||||||
runCmd := parent.NewSubCommand("run", "Run a LinuxKit image or template")
|
|
||||||
runCmd.LongDescription("Runs a LinuxKit image as a VM using the available hypervisor.\n\n" +
|
|
||||||
"Supported image formats: .iso, .qcow2, .vmdk, .raw\n\n" +
|
"Supported image formats: .iso, .qcow2, .vmdk, .raw\n\n" +
|
||||||
"You can also run from a template using --template, which will build and run\n" +
|
"You can also run from a template using --template, which will build and run\n" +
|
||||||
"the image automatically. Use --var to set template variables.\n\n" +
|
"the image automatically. Use --var to set template variables.\n\n" +
|
||||||
|
|
@ -35,40 +37,41 @@ func addVMRunCommand(parent *clir.Command) {
|
||||||
" core vm run -d image.qcow2\n" +
|
" core vm run -d image.qcow2\n" +
|
||||||
" core vm run --name myvm --memory 2048 --cpus 4 image.iso\n" +
|
" core vm run --name myvm --memory 2048 --cpus 4 image.iso\n" +
|
||||||
" core vm run --template core-dev --var SSH_KEY=\"ssh-rsa AAAA...\"\n" +
|
" core vm run --template core-dev --var SSH_KEY=\"ssh-rsa AAAA...\"\n" +
|
||||||
" core vm run --template server-php --var SSH_KEY=\"...\" --var DOMAIN=example.com")
|
" core vm run --template server-php --var SSH_KEY=\"...\" --var DOMAIN=example.com",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
runCmd.StringFlag("name", "Name for the container", &name)
|
|
||||||
runCmd.BoolFlag("d", "Run in detached mode (background)", &detach)
|
|
||||||
runCmd.IntFlag("memory", "Memory in MB (default: 1024)", &memory)
|
|
||||||
runCmd.IntFlag("cpus", "Number of CPUs (default: 1)", &cpus)
|
|
||||||
runCmd.IntFlag("ssh-port", "SSH port for exec commands (default: 2222)", &sshPort)
|
|
||||||
runCmd.StringFlag("template", "Run from a LinuxKit template (build + run)", &templateName)
|
|
||||||
runCmd.StringsFlag("var", "Template variable in KEY=VALUE format (can be repeated)", &varFlags)
|
|
||||||
|
|
||||||
runCmd.Action(func() error {
|
|
||||||
opts := container.RunOptions{
|
opts := container.RunOptions{
|
||||||
Name: name,
|
Name: runName,
|
||||||
Detach: detach,
|
Detach: runDetach,
|
||||||
Memory: memory,
|
Memory: runMemory,
|
||||||
CPUs: cpus,
|
CPUs: runCPUs,
|
||||||
SSHPort: sshPort,
|
SSHPort: runSSHPort,
|
||||||
}
|
}
|
||||||
|
|
||||||
// If template is specified, build and run from template
|
// If template is specified, build and run from template
|
||||||
if templateName != "" {
|
if runTemplateName != "" {
|
||||||
vars := ParseVarFlags(varFlags)
|
vars := ParseVarFlags(runVarFlags)
|
||||||
return RunFromTemplate(templateName, vars, opts)
|
return RunFromTemplate(runTemplateName, vars, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, require an image path
|
// Otherwise, require an image path
|
||||||
args := runCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("image path is required (or use --template)")
|
return fmt.Errorf("image path is required (or use --template)")
|
||||||
}
|
}
|
||||||
image := args[0]
|
image := args[0]
|
||||||
|
|
||||||
return runContainer(image, name, detach, memory, cpus, sshPort)
|
return runContainer(image, runName, runDetach, runMemory, runCPUs, runSSHPort)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
runCmd.Flags().StringVar(&runName, "name", "", "Name for the container")
|
||||||
|
runCmd.Flags().BoolVarP(&runDetach, "detach", "d", false, "Run in detached mode (background)")
|
||||||
|
runCmd.Flags().IntVar(&runMemory, "memory", 0, "Memory in MB (default: 1024)")
|
||||||
|
runCmd.Flags().IntVar(&runCPUs, "cpus", 0, "Number of CPUs (default: 1)")
|
||||||
|
runCmd.Flags().IntVar(&runSSHPort, "ssh-port", 0, "SSH port for exec commands (default: 2222)")
|
||||||
|
runCmd.Flags().StringVar(&runTemplateName, "template", "", "Run from a LinuxKit template (build + run)")
|
||||||
|
runCmd.Flags().StringArrayVar(&runVarFlags, "var", nil, "Template variable in KEY=VALUE format (can be repeated)")
|
||||||
|
|
||||||
|
parent.AddCommand(runCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runContainer(image, name string, detach bool, memory, cpus, sshPort int) error {
|
func runContainer(image, name string, detach bool, memory, cpus, sshPort int) error {
|
||||||
|
|
@ -111,21 +114,25 @@ func runContainer(image, name string, detach bool, memory, cpus, sshPort int) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMPsCommand adds the 'ps' command under vm.
|
var psAll bool
|
||||||
func addVMPsCommand(parent *clir.Command) {
|
|
||||||
var all bool
|
|
||||||
|
|
||||||
psCmd := parent.NewSubCommand("ps", "List running VMs")
|
// addVMPsCommand adds the 'ps' command under vm.
|
||||||
psCmd.LongDescription("Lists all VMs. By default, only shows running VMs.\n\n" +
|
func addVMPsCommand(parent *cobra.Command) {
|
||||||
|
psCmd := &cobra.Command{
|
||||||
|
Use: "ps",
|
||||||
|
Short: "List running VMs",
|
||||||
|
Long: "Lists all VMs. By default, only shows running VMs.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core vm ps\n" +
|
" core vm ps\n" +
|
||||||
" core vm ps -a")
|
" core vm ps -a",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
return listContainers(psAll)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
psCmd.BoolFlag("a", "Show all containers (including stopped)", &all)
|
psCmd.Flags().BoolVarP(&psAll, "all", "a", false, "Show all containers (including stopped)")
|
||||||
|
|
||||||
psCmd.Action(func() error {
|
parent.AddCommand(psCmd)
|
||||||
return listContainers(all)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func listContainers(all bool) error {
|
func listContainers(all bool) error {
|
||||||
|
|
@ -207,20 +214,23 @@ func formatDuration(d time.Duration) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMStopCommand adds the 'stop' command under vm.
|
// addVMStopCommand adds the 'stop' command under vm.
|
||||||
func addVMStopCommand(parent *clir.Command) {
|
func addVMStopCommand(parent *cobra.Command) {
|
||||||
stopCmd := parent.NewSubCommand("stop", "Stop a running VM")
|
stopCmd := &cobra.Command{
|
||||||
stopCmd.LongDescription("Stops a running VM by ID.\n\n" +
|
Use: "stop <container-id>",
|
||||||
|
Short: "Stop a running VM",
|
||||||
|
Long: "Stops a running VM by ID.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core vm stop abc12345\n" +
|
" core vm stop abc12345\n" +
|
||||||
" core vm stop abc1")
|
" core vm stop abc1",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
stopCmd.Action(func() error {
|
|
||||||
args := stopCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("container ID is required")
|
return fmt.Errorf("container ID is required")
|
||||||
}
|
}
|
||||||
return stopContainer(args[0])
|
return stopContainer(args[0])
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(stopCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stopContainer(id string) error {
|
func stopContainer(id string) error {
|
||||||
|
|
@ -271,25 +281,28 @@ func resolveContainerID(manager *container.LinuxKitManager, partialID string) (s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMLogsCommand adds the 'logs' command under vm.
|
var logsFollow bool
|
||||||
func addVMLogsCommand(parent *clir.Command) {
|
|
||||||
var follow bool
|
|
||||||
|
|
||||||
logsCmd := parent.NewSubCommand("logs", "View VM logs")
|
// addVMLogsCommand adds the 'logs' command under vm.
|
||||||
logsCmd.LongDescription("View logs from a VM.\n\n" +
|
func addVMLogsCommand(parent *cobra.Command) {
|
||||||
|
logsCmd := &cobra.Command{
|
||||||
|
Use: "logs <container-id>",
|
||||||
|
Short: "View VM logs",
|
||||||
|
Long: "View logs from a VM.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core vm logs abc12345\n" +
|
" core vm logs abc12345\n" +
|
||||||
" core vm logs -f abc1")
|
" core vm logs -f abc1",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
logsCmd.BoolFlag("f", "Follow log output", &follow)
|
|
||||||
|
|
||||||
logsCmd.Action(func() error {
|
|
||||||
args := logsCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("container ID is required")
|
return fmt.Errorf("container ID is required")
|
||||||
}
|
}
|
||||||
return viewLogs(args[0], follow)
|
return viewLogs(args[0], logsFollow)
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
logsCmd.Flags().BoolVarP(&logsFollow, "follow", "f", false, "Follow log output")
|
||||||
|
|
||||||
|
parent.AddCommand(logsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func viewLogs(id string, follow bool) error {
|
func viewLogs(id string, follow bool) error {
|
||||||
|
|
@ -315,20 +328,23 @@ func viewLogs(id string, follow bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// addVMExecCommand adds the 'exec' command under vm.
|
// addVMExecCommand adds the 'exec' command under vm.
|
||||||
func addVMExecCommand(parent *clir.Command) {
|
func addVMExecCommand(parent *cobra.Command) {
|
||||||
execCmd := parent.NewSubCommand("exec", "Execute a command in a VM")
|
execCmd := &cobra.Command{
|
||||||
execCmd.LongDescription("Execute a command inside a running VM via SSH.\n\n" +
|
Use: "exec <container-id> <command> [args...]",
|
||||||
|
Short: "Execute a command in a VM",
|
||||||
|
Long: "Execute a command inside a running VM via SSH.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core vm exec abc12345 ls -la\n" +
|
" core vm exec abc12345 ls -la\n" +
|
||||||
" core vm exec abc1 /bin/sh")
|
" core vm exec abc1 /bin/sh",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
execCmd.Action(func() error {
|
|
||||||
args := execCmd.OtherArgs()
|
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return fmt.Errorf("container ID and command are required")
|
return fmt.Errorf("container ID and command are required")
|
||||||
}
|
}
|
||||||
return execInContainer(args[0], args[1:])
|
return execInContainer(args[0], args[1:])
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(execCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func execInContainer(id string, cmd []string) error {
|
func execInContainer(id string, cmd []string) error {
|
||||||
|
|
|
||||||
|
|
@ -10,63 +10,72 @@ import (
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
|
|
||||||
"github.com/host-uk/core/pkg/container"
|
"github.com/host-uk/core/pkg/container"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addVMTemplatesCommand adds the 'templates' command under vm.
|
// addVMTemplatesCommand adds the 'templates' command under vm.
|
||||||
func addVMTemplatesCommand(parent *clir.Command) {
|
func addVMTemplatesCommand(parent *cobra.Command) {
|
||||||
templatesCmd := parent.NewSubCommand("templates", "Manage LinuxKit templates")
|
templatesCmd := &cobra.Command{
|
||||||
templatesCmd.LongDescription("Manage LinuxKit YAML templates for building VMs.\n\n" +
|
Use: "templates",
|
||||||
|
Short: "Manage LinuxKit templates",
|
||||||
|
Long: "Manage LinuxKit YAML templates for building VMs.\n\n" +
|
||||||
"Templates provide pre-configured LinuxKit configurations for common use cases.\n" +
|
"Templates provide pre-configured LinuxKit configurations for common use cases.\n" +
|
||||||
"They support variable substitution with ${VAR} and ${VAR:-default} syntax.\n\n" +
|
"They support variable substitution with ${VAR} and ${VAR:-default} syntax.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core vm templates # List available templates\n" +
|
" core vm templates # List available templates\n" +
|
||||||
" core vm templates show core-dev # Show template content\n" +
|
" core vm templates show core-dev # Show template content\n" +
|
||||||
" core vm templates vars server-php # Show template variables")
|
" core vm templates vars server-php # Show template variables",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
// Default action: list templates
|
|
||||||
templatesCmd.Action(func() error {
|
|
||||||
return listTemplates()
|
return listTemplates()
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
// Add subcommands
|
// Add subcommands
|
||||||
addTemplatesShowCommand(templatesCmd)
|
addTemplatesShowCommand(templatesCmd)
|
||||||
addTemplatesVarsCommand(templatesCmd)
|
addTemplatesVarsCommand(templatesCmd)
|
||||||
|
|
||||||
|
parent.AddCommand(templatesCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTemplatesShowCommand adds the 'templates show' subcommand.
|
// addTemplatesShowCommand adds the 'templates show' subcommand.
|
||||||
func addTemplatesShowCommand(parent *clir.Command) {
|
func addTemplatesShowCommand(parent *cobra.Command) {
|
||||||
showCmd := parent.NewSubCommand("show", "Display template content")
|
showCmd := &cobra.Command{
|
||||||
showCmd.LongDescription("Display the content of a LinuxKit template.\n\n" +
|
Use: "show <template-name>",
|
||||||
|
Short: "Display template content",
|
||||||
|
Long: "Display the content of a LinuxKit template.\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core templates show core-dev\n" +
|
" core templates show core-dev\n" +
|
||||||
" core templates show server-php")
|
" core templates show server-php",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
showCmd.Action(func() error {
|
|
||||||
args := showCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("template name is required")
|
return fmt.Errorf("template name is required")
|
||||||
}
|
}
|
||||||
return showTemplate(args[0])
|
return showTemplate(args[0])
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(showCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addTemplatesVarsCommand adds the 'templates vars' subcommand.
|
// addTemplatesVarsCommand adds the 'templates vars' subcommand.
|
||||||
func addTemplatesVarsCommand(parent *clir.Command) {
|
func addTemplatesVarsCommand(parent *cobra.Command) {
|
||||||
varsCmd := parent.NewSubCommand("vars", "Show template variables")
|
varsCmd := &cobra.Command{
|
||||||
varsCmd.LongDescription("Display all variables used in a template.\n\n" +
|
Use: "vars <template-name>",
|
||||||
|
Short: "Show template variables",
|
||||||
|
Long: "Display all variables used in a template.\n\n" +
|
||||||
"Shows required variables (no default) and optional variables (with defaults).\n\n" +
|
"Shows required variables (no default) and optional variables (with defaults).\n\n" +
|
||||||
"Examples:\n" +
|
"Examples:\n" +
|
||||||
" core templates vars core-dev\n" +
|
" core templates vars core-dev\n" +
|
||||||
" core templates vars server-php")
|
" core templates vars server-php",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
varsCmd.Action(func() error {
|
|
||||||
args := varsCmd.OtherArgs()
|
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
return fmt.Errorf("template name is required")
|
return fmt.Errorf("template name is required")
|
||||||
}
|
}
|
||||||
return showTemplateVars(args[0])
|
return showTemplateVars(args[0])
|
||||||
})
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.AddCommand(varsCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func listTemplates() error {
|
func listTemplates() error {
|
||||||
|
|
|
||||||
14
cmd/vm/vm.go
14
cmd/vm/vm.go
|
|
@ -4,7 +4,7 @@ package vm
|
||||||
import (
|
import (
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/host-uk/core/cmd/shared"
|
"github.com/host-uk/core/cmd/shared"
|
||||||
"github.com/leaanthony/clir"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Style aliases from shared
|
// Style aliases from shared
|
||||||
|
|
@ -22,9 +22,11 @@ var (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddVMCommands adds container-related commands under 'vm' to the CLI.
|
// AddVMCommands adds container-related commands under 'vm' to the CLI.
|
||||||
func AddVMCommands(parent *clir.Cli) {
|
func AddVMCommands(root *cobra.Command) {
|
||||||
vmCmd := parent.NewSubCommand("vm", "LinuxKit VM management")
|
vmCmd := &cobra.Command{
|
||||||
vmCmd.LongDescription("Manage LinuxKit virtual machines.\n\n" +
|
Use: "vm",
|
||||||
|
Short: "LinuxKit VM management",
|
||||||
|
Long: "Manage LinuxKit virtual machines.\n\n" +
|
||||||
"LinuxKit VMs are lightweight, immutable VMs built from YAML templates.\n" +
|
"LinuxKit VMs are lightweight, immutable VMs built from YAML templates.\n" +
|
||||||
"They run using qemu or hyperkit depending on your system.\n\n" +
|
"They run using qemu or hyperkit depending on your system.\n\n" +
|
||||||
"Commands:\n" +
|
"Commands:\n" +
|
||||||
|
|
@ -33,8 +35,10 @@ func AddVMCommands(parent *clir.Cli) {
|
||||||
" stop Stop a running VM\n" +
|
" stop Stop a running VM\n" +
|
||||||
" logs View VM logs\n" +
|
" logs View VM logs\n" +
|
||||||
" exec Execute command in VM\n" +
|
" exec Execute command in VM\n" +
|
||||||
" templates Manage LinuxKit templates")
|
" templates Manage LinuxKit templates",
|
||||||
|
}
|
||||||
|
|
||||||
|
root.AddCommand(vmCmd)
|
||||||
addVMRunCommand(vmCmd)
|
addVMRunCommand(vmCmd)
|
||||||
addVMPsCommand(vmCmd)
|
addVMPsCommand(vmCmd)
|
||||||
addVMStopCommand(vmCmd)
|
addVMStopCommand(vmCmd)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue