You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Cobra的命令行工具:实现prepare与run命令的属性共享

Great question! When building a Cobra-based CLI where one command depends on the output of another, you have a few solid approaches to share state between prepare and run. Let’s break down the most practical ones, with code examples tailored to your use case:

1. Persist State to a Configuration File (Most Reliable)

This is the go-to approach for cross-command state sharing in CLI tools. The prepare command writes critical data (like temp directory path, file validation status) to a structured config file (JSON/YAML), and run reads this file to get what it needs.

Step 1: Define a Shared State Structure

Create a reusable package to hold your state (e.g., internal/state/state.go):

package state

import "time"

type RunContext struct {
    TempDir      string    `json:"temp_dir"`
    FilesValid   bool      `json:"files_valid"`
    CreatedAt    time.Time `json:"created_at"`
}

Step 2: Update prepare to Write State

Modify your prepare command's RunE function to save state after completing its tasks:

func prepareCmdRun(cmd *cobra.Command, args []string) error {
    // 1. Create temp directory
    tempDir, err := os.MkdirTemp("", "btr-*")
    if err != nil {
        return fmt.Errorf("failed to create temp dir: %w", err)
    }

    // 2. Validate files (replace with your actual logic)
    filesValid := validateRequiredFiles()

    // 3. Build and save state
    ctx := state.RunContext{
        TempDir:    tempDir,
        FilesValid: filesValid,
        CreatedAt:  time.Now(),
    }

    // Get standard config directory (works across OSes)
    configDir, err := os.UserConfigDir()
    if err != nil {
        return fmt.Errorf("failed to get config directory: %w", err)
    }
    configPath := filepath.Join(configDir, "btr", "context.json")

    // Create parent directory if it doesn't exist
    if err := os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
        return fmt.Errorf("failed to create config directory: %w", err)
    }

    // Serialize state to JSON
    data, err := json.MarshalIndent(ctx, "", "  ")
    if err != nil {
        return fmt.Errorf("failed to serialize state: %w", err)
    }

    if err := os.WriteFile(configPath, data, 0644); err != nil {
        return fmt.Errorf("failed to write state file: %w", err)
    }

    fmt.Printf("✅ Prepare complete! Temp directory: %s\n", tempDir)
    return nil
}

Step 3: Update run to Read and Validate State

Modify your run command to load the state, and add checks to ensure prepare was run successfully:

func runCmdRun(cmd *cobra.Command, args []string) error {
    // Get config path (match what's used in prepare)
    configDir, err := os.UserConfigDir()
    if err != nil {
        return fmt.Errorf("failed to get config directory: %w", err)
    }
    configPath := filepath.Join(configDir, "btr", "context.json")

    // Check if state file exists
    if _, err := os.Stat(configPath); os.IsNotExist(err) {
        return fmt.Errorf("❌ Prepare has not been run yet! Please execute `btr prepare` first")
    }

    // Read and deserialize state
    data, err := os.ReadFile(configPath)
    if err != nil {
        return fmt.Errorf("failed to read state file: %w", err)
    }

    var ctx state.RunContext
    if err := json.Unmarshal(data, &ctx); err != nil {
        return fmt.Errorf("failed to parse state file: %w", err)
    }

    // Add optional validity checks
    if time.Since(ctx.CreatedAt) > 2*time.Hour {
        return fmt.Errorf("❌ Prepare state is outdated (older than 2 hours)! Re-run `btr prepare`")
    }
    if !ctx.FilesValid {
        return fmt.Errorf("❌ Previous prepare step found invalid files! Fix issues and re-run `btr prepare`")
    }

    // Use the state in your run logic
    fmt.Printf("🚀 Starting run with temp directory: %s\n", ctx.TempDir)
    // ... your core run command logic here ...

    return nil
}

2. Use Environment Variables (Lightweight Option)

If you prefer not to write files, you can have prepare output shell commands to set environment variables, which run can then read. Note that this only works for the current shell session.

prepare Command Output

func prepareCmdRun(cmd *cobra.Command, args []string) error {
    tempDir, err := os.MkdirTemp("", "btr-*")
    if err != nil {
        return fmt.Errorf("failed to create temp dir: %w", err)
    }
    filesValid := validateRequiredFiles()

    // Print export commands for the user to source
    fmt.Printf("export BTR_TEMP_DIR=\"%s\"\n", tempDir)
    fmt.Printf("export BTR_FILES_VALID=\"%t\"\n", filesValid)
    fmt.Println("\n💡 Run `eval $(btr prepare)` to set these variables automatically")
    return nil
}

run Command Read

func runCmdRun(cmd *cobra.Command, args []string) error {
    tempDir := os.Getenv("BTR_TEMP_DIR")
    if tempDir == "" {
        return fmt.Errorf("❌ BTR_TEMP_DIR not set! Run `eval $(btr prepare)` first")
    }

    filesValidStr := os.Getenv("BTR_FILES_VALID")
    filesValid, err := strconv.ParseBool(filesValidStr)
    if err != nil || !filesValid {
        return fmt.Errorf("❌ Files are invalid or BTR_FILES_VALID not set! Re-run `btr prepare`")
    }

    // Execute run logic
    fmt.Printf("🚀 Running with temp dir: %s\n", tempDir)
    return nil
}

3. Optional: Auto-Run Prepare from run

For user convenience, you can add a flag to run that automatically triggers prepare if no state exists:

func init() {
    runCmd.Flags().BoolP("auto-prepare", "a", false, "Automatically run prepare if not completed")
}

func runCmdRun(cmd *cobra.Command, args []string) error {
    autoPrepare, _ := cmd.Flags().GetBool("auto-prepare")
    configPath := getConfigPath() // Reuse the path logic from earlier

    if _, err := os.Stat(configPath); os.IsNotExist(err) {
        if autoPrepare {
            fmt.Println("🔧 Prepare not found, running automatically...")
            if err := prepareCmdRun(cmd, args); err != nil {
                return err
            }
        } else {
            return fmt.Errorf("❌ Prepare not run! Use `btr prepare` or `btr run --auto-prepare`")
        }
    }

    // Load state and proceed with run logic...
    return nil
}

Key Best Practices

  • Always add validation checks in run (e.g., state freshness, file validity) to avoid using stale or invalid data.
  • Let users override the config file path with a flag (e.g., --config) for flexibility.
  • Add a btr clean command to delete the state file and temp directory when needed.

内容的提问来源于stack exchange,提问作者user6124024

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:01:05