hako/commands.go
2025-06-10 22:26:39 -03:00

295 lines
7.3 KiB
Go

package main
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
)
func getContainerForCurrentDirectory() (string, error) {
// Get all hako containers
output, err := runCommandOutput("docker", "ps", "-a", "--filter", "name=hako-", "--format", "{{.Names}}")
if err != nil {
return "", err
}
containers := strings.Split(strings.TrimSpace(output), "\n")
if len(containers) == 0 || containers[0] == "" {
return "", fmt.Errorf("no hako containers found")
}
// Try to find containers that match any supported language for the current directory
supportedLangs := []string{"go", "py", "python"}
for _, lang := range supportedLangs {
expectedName, err := getProjectContainerName(lang)
if err != nil {
continue // Skip if we can't generate a name for this language
}
// Check if this expected container name exists in our list
for _, container := range containers {
if strings.TrimSpace(container) == expectedName {
return expectedName, nil
}
}
}
// If no exact match found, return the first container as fallback
return strings.TrimSpace(containers[0]), nil
}
func initCommand(lang string) error {
if lang == "" {
fmt.Println("Building base hako image...")
return buildBaseImage()
}
if !imageExists("hako-userland") {
fmt.Println("Base image not found, building it first...")
if err := buildBaseImage(); err != nil {
return err
}
} else {
// Check base image version
checkVersionMismatch("hako-userland")
}
fmt.Printf("Building %s language image...\n", lang)
return buildLanguageImage(lang)
}
func upCommand(lang string) error {
containerName, err := getProjectContainerName(lang)
if err != nil {
return err
}
imageName := fmt.Sprintf("hako-userland-%s", lang)
if !imageExists(imageName) {
return fmt.Errorf("image %s not found, run 'hako init %s' first", imageName, lang)
}
// Check for version mismatch
checkVersionMismatch(imageName)
cwd, err := os.Getwd()
if err != nil {
return err
}
if containerExists(containerName) {
if !containerIsRunning(containerName) {
fmt.Printf("Starting existing container %s...\n", containerName)
if err := startContainer(containerName); err != nil {
return err
}
} else {
fmt.Printf("Container %s is already running\n", containerName)
}
} else {
fmt.Printf("Creating new container %s...\n", containerName)
if err := createContainer(containerName, imageName, cwd); err != nil {
return err
}
if err := startContainer(containerName); err != nil {
return err
}
fmt.Printf("Copying workspace to container...\n")
if err := copyWorkspaceToContainer(containerName); err != nil {
return err
}
}
fmt.Printf("Entering container %s...\n", containerName)
return execContainer(containerName)
}
func downCommand(container string) error {
var containerName string
if container != "" {
containerName = container
} else {
// Find the appropriate hako container for current directory
var err error
containerName, err = getContainerForCurrentDirectory()
if err != nil {
return fmt.Errorf("no hako containers found for current project: %v", err)
}
}
if !containerExists(containerName) {
return fmt.Errorf("container %s does not exist", containerName)
}
fmt.Printf("Stopping container %s...\n", containerName)
if containerIsRunning(containerName) {
if err := stopContainer(containerName); err != nil {
return err
}
}
fmt.Printf("Removing container %s...\n", containerName)
return removeContainer(containerName)
}
func psCommand() error {
fmt.Println("Hako containers:")
if err := listHakoContainers(); err != nil {
return err
}
// Check for version mismatches in running containers
output, err := runCommandOutput("docker", "ps", "-a", "--filter", "name=hako-", "--format", "{{.Image}}")
if err == nil {
images := strings.Split(strings.TrimSpace(output), "\n")
checkedImages := make(map[string]bool)
for _, image := range images {
if image != "" && !checkedImages[image] {
checkVersionMismatch(image)
checkedImages[image] = true
}
}
}
return nil
}
func syncCommand() error {
// Find the appropriate hako container for current directory
containerName, err := getContainerForCurrentDirectory()
if err != nil {
return fmt.Errorf("no hako containers found for current project: %v", err)
}
if !containerExists(containerName) {
return fmt.Errorf("container %s does not exist", containerName)
}
fmt.Printf("Syncing files from container %s...\n", containerName)
tempDir, err := os.MkdirTemp("", "hako-sync-*")
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
if err := copyFromContainer(containerName, "/workspace/.", tempDir); err != nil {
return err
}
return syncFiles(tempDir, ".")
}
func syncFiles(srcDir, destDir string) error {
gitignore, err := NewGitIgnore()
if err != nil {
return fmt.Errorf("failed to parse .gitignore: %v", err)
}
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return fmt.Errorf("failed to walk %q: %v", path, err)
}
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return fmt.Errorf("failed to calculate relative path for %q: %v", path, err)
}
// Skip .git directory and its contents
if relPath == ".git" {
return filepath.SkipDir
}
// Skip files that start with .git (like .gitignore, .gitmodules, etc.)
if strings.HasPrefix(filepath.Base(relPath), ".git") {
return nil
}
// Check if the file should be ignored according to .gitignore
if gitignore.IsIgnored(relPath) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
destPath := filepath.Join(destDir, relPath)
if info.IsDir() {
return os.MkdirAll(destPath, info.Mode())
}
fmt.Println("cp", path, destPath)
srcFile, err := os.Open(path)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to open %s: %v\n", path, err)
return nil
}
defer srcFile.Close()
destFile, err := os.Create(destPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to create %s: %v\n", destPath, err)
return nil
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: failed to copy %s to %s: %v\n", path, destPath, err)
}
return nil
})
}
func copyWorkspaceToContainer(containerName string) error {
// Get all files that should be copied (tracked + untracked, excluding ignored)
trackedFiles, err := runCommandOutput("git", "ls-files")
if err != nil {
return err
}
untrackedFiles, err := runCommandOutput("git", "ls-files", "--others", "--exclude-standard")
if err != nil {
return err
}
allFiles := []string{}
if trackedFiles != "" {
allFiles = append(allFiles, strings.Split(trackedFiles, "\n")...)
}
if untrackedFiles != "" {
allFiles = append(allFiles, strings.Split(untrackedFiles, "\n")...)
}
if len(allFiles) == 0 {
return fmt.Errorf("no files to copy")
}
// Copy files one by one to preserve directory structure
for _, file := range allFiles {
if file == "" {
continue
}
// Create directory structure in container if needed
dir := filepath.Dir(file)
if dir != "." {
if err := runCommand("docker", "exec", containerName, "mkdir", "-p", "/workspace/"+dir); err != nil {
return err
}
}
// Copy the file
if err := copyToContainer(containerName, file, "/workspace/"+file); err != nil {
return fmt.Errorf("failed to copy %s: %v", file, err)
}
}
return nil
}