package cmd
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/spf13/cobra"
)
var (
deployTarget string
deployBackend string
deployDryRun bool
)
var deployCmd = &cobra.Command{
Use: "deploy",
Short: "Deploy the built site via rsync or rclone",
RunE: runDeploy,
}
func init() {
deployCmd.Flags().StringVar(&deployTarget, "target", "", "remote target (e.g. user@host:/var/www)")
deployCmd.Flags().StringVar(&deployBackend, "backend", "rsync", "deployment tool: rsync|rclone")
deployCmd.Flags().BoolVar(&deployDryRun, "dry-run", false, "print commands without executing")
}
func runDeploy(cmd *cobra.Command, args []string) error {
if deployTarget == "" {
return fmt.Errorf("--target is required")
}
src, err := filepath.Abs("public")
if err != nil {
return err
}
if _, err := os.Stat(src); err != nil {
return fmt.Errorf("build output not found at %s: run tilstream build first", src)
}
switch strings.ToLower(deployBackend) {
case "rsync":
return runRsync(cmd, src, deployTarget)
case "rclone":
return runRclone(cmd, src, deployTarget)
default:
return fmt.Errorf("unknown backend %q", deployBackend)
}
}
func runRsync(cmd *cobra.Command, src, target string) error {
argv := []string{
"rsync",
"-avz",
"--delete",
"--checksum",
src + "/",
target,
}
return runOrDryRun(cmd, argv)
}
func runRclone(cmd *cobra.Command, src, target string) error {
argv := []string{"rclone", "sync", src, target, "--progress"}
return runOrDryRun(cmd, argv)
}
func runOrDryRun(cmd *cobra.Command, argv []string) error {
if deployDryRun {
fmt.Fprintln(cmd.OutOrStdout(), strings.Join(argv, " "))
return nil
}
c := exec.CommandContext(cmd.Context(), argv[0], argv[1:]...)
c.Stdout = cmd.OutOrStdout()
c.Stderr = cmd.ErrOrStderr()
c.Stdin = os.Stdin
return c.Run()
}
// DeployBackends lists the supported backends. Exposed so shell completion
// can suggest them.
func DeployBackends() []string { return []string{"rsync", "rclone"} }
// validateTarget is exposed for tests.
func validateTarget(s string) error {
if s == "" {
return fmt.Errorf("empty target")
}
if !strings.ContainsAny(s, ":@/") {
return fmt.Errorf("target %q looks like a local path; specify host:path", s)
}
return nil
}