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