From 8094639aa2d5095af512d4e943fcb4af801aef07 Mon Sep 17 00:00:00 2001 From: Clawd Date: Sun, 15 Feb 2026 18:49:30 -0800 Subject: feat(v2): add CLI structure and deploy orchestration - cmd/ship/root_v2.go: new CLI with ship [PATH] as primary command - cmd/ship/deploy_v2.go: deploy orchestration with context struct - Placeholder implementations for static/docker/binary deploys - Placeholder subcommands (list, status, logs, remove, host) - Support for --name, --health, --ttl, --env flags - SHIP_PRETTY env var support Next: implement actual deploy flows --- cmd/ship/root_v2.go | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 cmd/ship/root_v2.go (limited to 'cmd/ship/root_v2.go') diff --git a/cmd/ship/root_v2.go b/cmd/ship/root_v2.go new file mode 100644 index 0000000..dab63be --- /dev/null +++ b/cmd/ship/root_v2.go @@ -0,0 +1,158 @@ +package main + +import ( + "os" + + "github.com/bdw/ship/internal/output" + "github.com/spf13/cobra" +) + +// This file defines the v2 CLI structure. +// The primary command is: ship [PATH] [FLAGS] +// All output is JSON by default. + +var rootV2Cmd = &cobra.Command{ + Use: "ship [PATH]", + Short: "Deploy code to a VPS. JSON output for agents.", + Long: `Ship deploys code to a VPS. Point it at a directory or binary, get a URL back. + + ship ./myproject # auto-detect and deploy + ship ./site --name docs # deploy with specific name + ship ./api --health /healthz # deploy with health check + ship ./preview --ttl 24h # deploy with auto-expiry + +All output is JSON. Use --pretty for human-readable output.`, + Args: cobra.MaximumNArgs(1), + RunE: runDeployV2, + SilenceUsage: true, + SilenceErrors: true, + DisableAutoGenTag: true, +} + +func initV2() { + // Global flags + rootV2Cmd.PersistentFlags().StringVar(&hostFlag, "host", "", "VPS host (SSH config alias or user@host)") + rootV2Cmd.PersistentFlags().BoolVar(&output.Pretty, "pretty", false, "Human-readable output") + + // Deploy flags + rootV2Cmd.Flags().String("name", "", "Deploy name (becomes subdomain)") + rootV2Cmd.Flags().String("health", "", "Health check endpoint (e.g., /healthz)") + rootV2Cmd.Flags().String("ttl", "", "Auto-delete after duration (e.g., 1h, 7d)") + rootV2Cmd.Flags().StringArray("env", nil, "Environment variable (KEY=VALUE)") + rootV2Cmd.Flags().String("env-file", "", "Path to .env file") + + // Check for SHIP_PRETTY env var + if os.Getenv("SHIP_PRETTY") == "1" { + output.Pretty = true + } + + // Add subcommands + rootV2Cmd.AddCommand(listV2Cmd) + rootV2Cmd.AddCommand(statusV2Cmd) + rootV2Cmd.AddCommand(logsV2Cmd) + rootV2Cmd.AddCommand(removeV2Cmd) + rootV2Cmd.AddCommand(hostV2Cmd) +} + +func runDeployV2(cmd *cobra.Command, args []string) error { + path := "." + if len(args) > 0 { + path = args[0] + } + + opts := deployV2Options{ + Host: hostFlag, + Pretty: output.Pretty, + } + + // Get flag values + opts.Name, _ = cmd.Flags().GetString("name") + opts.Health, _ = cmd.Flags().GetString("health") + opts.TTL, _ = cmd.Flags().GetString("ttl") + opts.Env, _ = cmd.Flags().GetStringArray("env") + opts.EnvFile, _ = cmd.Flags().GetString("env-file") + + // deployV2 handles all output and exits + deployV2(path, opts) + + // Should not reach here (deployV2 calls os.Exit) + return nil +} + +// Placeholder subcommands - to be implemented + +var listV2Cmd = &cobra.Command{ + Use: "list", + Short: "List all deployments", + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: implement + output.PrintAndExit(&output.ListResponse{ + Status: "ok", + Deploys: []output.DeployInfo{}, + }) + return nil + }, +} + +var statusV2Cmd = &cobra.Command{ + Use: "status NAME", + Short: "Check status of a deployment", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: implement + output.PrintAndExit(output.Err(output.ErrNotFound, "not implemented")) + return nil + }, +} + +var logsV2Cmd = &cobra.Command{ + Use: "logs NAME", + Short: "View logs for a deployment", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: implement + output.PrintAndExit(output.Err(output.ErrNotFound, "not implemented")) + return nil + }, +} + +var removeV2Cmd = &cobra.Command{ + Use: "remove NAME", + Short: "Remove a deployment", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: implement + output.PrintAndExit(output.Err(output.ErrNotFound, "not implemented")) + return nil + }, +} + +var hostV2Cmd = &cobra.Command{ + Use: "host", + Short: "Manage VPS host", +} + +func init() { + hostV2Cmd.AddCommand(hostInitV2Cmd) + hostV2Cmd.AddCommand(hostStatusV2Cmd) +} + +var hostInitV2Cmd = &cobra.Command{ + Use: "init USER@HOST --domain DOMAIN", + Short: "Initialize a VPS for deployments", + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: implement - this is critical functionality to preserve + output.PrintAndExit(output.Err(output.ErrNotFound, "not implemented")) + return nil + }, +} + +var hostStatusV2Cmd = &cobra.Command{ + Use: "status", + Short: "Check host status", + RunE: func(cmd *cobra.Command, args []string) error { + // TODO: implement + output.PrintAndExit(output.Err(output.ErrNotFound, "not implemented")) + return nil + }, +} -- cgit v1.2.3