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 }