Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions cmd/devcontainerx/openincode.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,78 @@ package main
import (
"fmt"
"os/exec"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"github.com/stuartleeks/devcontainer-cli/internal/pkg/devcontainers"
"github.com/stuartleeks/devcontainer-cli/internal/pkg/wsl"
)

func createOpenInCodeCommand() *cobra.Command {
var subFolder string
cmd := &cobra.Command{
Use: "open-in-code <path>",
Short: "open the specified path devcontainer project in VS Code",
Long: "Open the specified path (containing a .devcontainer folder in VS Code",
RunE: func(cmd *cobra.Command, args []string) error {
return launchDevContainer(cmd, "code", args)
return launchDevContainer(cmd, "code", args, subFolder)
},
}
cmd.Flags().StringVarP(&subFolder, "sub-folder", "s", "", "sub-folder within the devcontainer to open")
return cmd
}
func createOpenInCodeInsidersCommand() *cobra.Command {
var subFolder string
cmd := &cobra.Command{
Use: "open-in-code-insiders <path>",
Short: "open the specified path devcontainer project in VS Code Insiders",
Long: "Open the specified path (containing a .devcontainer folder in VS Code Insiders",
RunE: func(cmd *cobra.Command, args []string) error {
return launchDevContainer(cmd, "code-insiders", args)
return launchDevContainer(cmd, "code-insiders", args, subFolder)
},
}
cmd.Flags().StringVarP(&subFolder, "sub-folder", "s", "", "sub-folder within the devcontainer to open")
return cmd
}

func launchDevContainer(cmd *cobra.Command, appBase string, args []string) error {
func launchDevContainer(cmd *cobra.Command, appBase string, args []string, subFolder string) error {
if len(args) > 1 {
return cmd.Usage()
}
path := "." // default to current directory
if len(args) == 1 {
if len(args) >= 1 {
path = args[0]
}

launchURI, err := devcontainers.GetDevContainerURI(path)
// allow the command to be invoked from a subfolder of the folder containing the devcontainer.json by searching ancestor paths for a devcontainer.json
devcontainerPath, err := devcontainers.FindDevContainerInAncestorPaths(path)
if err != nil {
return fmt.Errorf("error finding devcontainer.json: %s", err)
}
fmt.Printf("Found devcontainer.json at %s\n", devcontainerPath)

// If subFolder is not empty then use that as the path to open in VS Code (note it should be relative to the folder containing the devcontainer.json/.devcontainer folder)
// If subFolder is empty and the current folder is contained within the folder containing the devcontainer.json, then open the current folder in VS Code, otherwise open the folder containing the devcontainer.json in VS Code
if subFolder == "" {
absDevContainerPath, err := filepath.Abs(devcontainerPath)
if err != nil {
return fmt.Errorf("error getting absolute path: %s", err)
}
absCurrentPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("error getting absolute path: %s", err)
}
if absCurrentPath != absDevContainerPath && strings.HasPrefix(absCurrentPath, absDevContainerPath) {
subFolder, err = filepath.Rel(absDevContainerPath, absCurrentPath)
if err != nil {
return fmt.Errorf("error getting relative path: %s", err)
}
fmt.Printf("Opening subfolder %s within devcontainer\n", subFolder)
}
}

launchURI, err := devcontainers.GetDevContainerURI(devcontainerPath, subFolder)
if err != nil {
return err
}
Expand Down
7 changes: 6 additions & 1 deletion docs/open-in-code.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

The `devcontainer open-in-code` command opens the current folder in VS Code as a dev container, i.e. it skips the normal step of opening in VS Code and then clicking # on the "Re-open in container" prompt to reload the window as a dev container. With `devcontainer open-in-code` you get straight to the dev container!

You can also use `devcontainer open-in-code <path>` to open a different folder as a devcontainer.
You can also use `devcontainer open-in-code <path>` to open a the devcontainer from a different folder.

The command also supports the `--sub-folder` option. This allows you to specify a sub-folder within the folder that contains the devcontainer configuration (i.e. with the `.devcontainer` folder or `.devcontainer.json` file).

The `open-in-code` command will walk up the tree from the current/specified folder until it finds a folder with a devcontainer configuration, and then open that folder in VS Code as a dev container.
If `--sub-folder` is not specified and the devcontainer configuration is found in a parent folder, relative path to the current folder is used as the sub-folder.

If you want to use the VS Code Insiders release, you can use `devcontainer open-in-code-insiders`.
35 changes: 35 additions & 0 deletions internal/pkg/devcontainers/devcontainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,38 @@ func getDevContainerJsonPath(folderPath string) (string, error) {

return "", fmt.Errorf("devcontainer.json not found. Looked for %s", strings.Join(pathsToTest, ","))
}

func FindDevContainerInAncestorPaths(folderPath string) (string, error) {
currentPath, err := filepath.Abs(folderPath)
if err != nil {
return "", fmt.Errorf("error getting absolute path: %w", err)
}

for {
// Check if devcontainer.json exists in current path
_, err := getDevContainerJsonPath(currentPath)
if err == nil {
return currentPath, nil
}

// Check if this is a git repository root
gitPath := filepath.Join(currentPath, ".git")
gitInfo, gitErr := os.Stat(gitPath)
isGitRoot := gitErr == nil && gitInfo.IsDir()

// If we're at a git root, stop searching (we already checked this folder)
if isGitRoot {
return "", fmt.Errorf("devcontainer.json not found in ancestor paths")
}

// Move to parent directory
parentPath := filepath.Dir(currentPath)

// Check if we've reached the root (parent is same as current)
if parentPath == currentPath {
return "", fmt.Errorf("devcontainer.json not found in ancestor paths")
}

currentPath = parentPath
}
}
6 changes: 5 additions & 1 deletion internal/pkg/devcontainers/remoteuri.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import (
)

// GetDevContainerURI gets the devcontainer URI for a folder to launch using the VS Code --folder-uri switch
func GetDevContainerURI(folderPath string) (string, error) {
// If subFolder is specified, it is appended to the workspaceMountPath
func GetDevContainerURI(folderPath string, subFolder string) (string, error) {

absPath, err := filepath.Abs(folderPath)
if err != nil {
Expand All @@ -33,6 +34,9 @@ func GetDevContainerURI(folderPath string) (string, error) {
if err != nil {
return "", err
}
if subFolder != "" {
workspaceMountPath = filepath.Join(workspaceMountPath, subFolder)
}
uri := fmt.Sprintf("vscode-remote://dev-container+%s%s", launchPathHex, workspaceMountPath)

return uri, nil
Expand Down