dev.go

77 lines
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
package commands

import (
	"cmp"
	"flag"
	"fmt"
	"log"
	"net"
	"os"
	"os/exec"
	"path/filepath"
)

const devUsage = `Start a development server

Usage:
  congo dev [flags]

Flags:
  --port <port>    Override server port (default: 5000)

The server runs with ENV=development. Press Ctrl+C to stop.
`

func Dev() {
	fs := flag.NewFlagSet("dev", flag.ExitOnError)
	fs.Usage = func() { fmt.Print(devUsage) }

	port := fs.String("port", "", "Override server port")

	fs.Parse(os.Args[2:])

	dir := findAppDir()

	env := append(os.Environ(), "ENV=development")
	if *port != "" {
		env = append(env, "PORT="+*port)
	}

	listenPort := cmp.Or(*port, os.Getenv("PORT"), "5000")

	// Check for port conflicts before starting.
	if conn, err := net.Dial("tcp", "localhost:"+listenPort); err == nil {
		conn.Close()
		log.Fatalf("Port %s is already in use. Try:\n  congo dev --port <other-port>", listenPort)
	}

	fmt.Printf("Starting development server from ./%s\n", dir)
	fmt.Printf("  http://localhost:%s\n\n", listenPort)

	cmd := exec.Command("go", "run", "./"+dir)
	cmd.Env = env
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Stdin = os.Stdin
	if err := cmd.Run(); err != nil {
		if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 2 {
			fmt.Fprintf(os.Stderr, "\nBuild failed. Check the errors above.\n")
		}
		os.Exit(1)
	}
}

// findAppDir locates the app directory containing main.go.
// Checks common names, then falls back to root.
func findAppDir() string {
	for _, dir := range []string{"web", "app", "api", "cmd"} {
		if _, err := os.Stat(filepath.Join(dir, "main.go")); err == nil {
			return dir
		}
	}
	if _, err := os.Stat("main.go"); err == nil {
		return "."
	}
	log.Fatal("No main.go found in web/, app/, api/, cmd/, or root.\nRun 'congo init <name>' to create a project.")
	return ""
}