Skip to content

Commit dd847bc

Browse files
committed
Enhance project configuration management: add lock file generation on project updates, apply commands, and devcontainer generation; streamline Docker client interactions.
1 parent d05c898 commit dd847bc

File tree

10 files changed

+389
-171
lines changed

10 files changed

+389
-171
lines changed

internal/commands/apply.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ var applyCmd = &cobra.Command{
124124
}
125125
}
126126

127+
_ = WriteLockFileForBox(proj.BoxName, projectName, proj.WorkspacePath, proj.BaseImage, "")
128+
127129
fmt.Println("✅ Applied lockfile: registries/sources configured and packages reconciled")
128130
return nil
129131
},

internal/commands/devcontainer.go

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package commands
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
type devContainer struct {
14+
Name string `json:"name,omitempty"`
15+
Image string `json:"image,omitempty"`
16+
WorkspaceFolder string `json:"workspaceFolder,omitempty"`
17+
ContainerEnv map[string]string `json:"containerEnv,omitempty"`
18+
PostCreateCommand string `json:"postCreateCommand,omitempty"`
19+
ForwardPorts []string `json:"forwardPorts,omitempty"`
20+
Mounts []string `json:"mounts,omitempty"`
21+
}
22+
23+
var devcontainerCmd = &cobra.Command{
24+
Use: "devcontainer",
25+
Short: "Generate VS Code devcontainer.json from devbox.json",
26+
Args: cobra.NoArgs,
27+
}
28+
29+
var devcontainerGenerateCmd = &cobra.Command{
30+
Use: "generate",
31+
Short: "Generate .devcontainer/devcontainer.json for the current project",
32+
Args: cobra.NoArgs,
33+
RunE: func(cmd *cobra.Command, args []string) error {
34+
cwd, err := os.Getwd()
35+
if err != nil {
36+
return fmt.Errorf("failed to get cwd: %w", err)
37+
}
38+
39+
pcfg, err := configManager.LoadProjectConfig(cwd)
40+
if err != nil {
41+
return fmt.Errorf("failed to load devbox project config: %w", err)
42+
}
43+
if pcfg == nil {
44+
return fmt.Errorf("no devbox project config found in %s (devbox.json | devbox.project.json | .devbox.json)", cwd)
45+
}
46+
47+
dc := devContainer{
48+
Name: pcfg.Name,
49+
Image: firstNonEmpty(pcfg.BaseImage, "ubuntu:22.04"),
50+
WorkspaceFolder: firstNonEmpty(pcfg.WorkingDir, "/workspace"),
51+
ContainerEnv: map[string]string{},
52+
}
53+
54+
for k, v := range pcfg.Environment {
55+
dc.ContainerEnv[k] = v
56+
}
57+
58+
for _, p := range pcfg.Ports {
59+
part := strings.TrimSpace(p)
60+
if part == "" {
61+
continue
62+
}
63+
64+
if i := strings.Index(part, ":"); i != -1 {
65+
part = part[i+1:]
66+
}
67+
if i := strings.Index(part, "/"); i != -1 {
68+
part = part[:i]
69+
}
70+
if part != "" {
71+
dc.ForwardPorts = append(dc.ForwardPorts, part)
72+
}
73+
}
74+
75+
dc.Mounts = append(dc.Mounts, "source=${localWorkspaceFolder},target="+dc.WorkspaceFolder+",type=bind,consistency=cached")
76+
77+
for _, vol := range pcfg.Volumes {
78+
s := strings.TrimSpace(vol)
79+
if s == "" || !strings.Contains(s, ":") {
80+
continue
81+
}
82+
parts := strings.SplitN(s, ":", 2)
83+
host := parts[0]
84+
target := parts[1]
85+
86+
if strings.HasPrefix(host, "~") {
87+
host = "${env:HOME}" + strings.TrimPrefix(host, "~")
88+
}
89+
dc.Mounts = append(dc.Mounts, fmt.Sprintf("source=%s,target=%s,type=bind", host, target))
90+
}
91+
92+
if len(pcfg.SetupCommands) > 0 {
93+
94+
dc.PostCreateCommand = strings.Join(pcfg.SetupCommands, " && ")
95+
}
96+
97+
outDir := filepath.Join(cwd, ".devcontainer")
98+
if err := os.MkdirAll(outDir, 0755); err != nil {
99+
return fmt.Errorf("failed to create .devcontainer dir: %w", err)
100+
}
101+
outPath := filepath.Join(outDir, "devcontainer.json")
102+
data, err := json.MarshalIndent(dc, "", " ")
103+
if err != nil {
104+
return fmt.Errorf("failed to marshal devcontainer.json: %w", err)
105+
}
106+
if err := os.WriteFile(outPath, data, 0644); err != nil {
107+
return fmt.Errorf("failed to write %s: %w", outPath, err)
108+
}
109+
110+
fmt.Printf("✅ Wrote %s\n", outPath)
111+
fmt.Println("Open the folder in VS Code and use 'Reopen in Container' to start a consistent dev environment.")
112+
return nil
113+
},
114+
}
115+
116+
func firstNonEmpty(vals ...string) string {
117+
for _, v := range vals {
118+
if strings.TrimSpace(v) != "" {
119+
return v
120+
}
121+
}
122+
return ""
123+
}
124+
125+
func init() {
126+
devcontainerCmd.AddCommand(devcontainerGenerateCmd)
127+
rootCmd.AddCommand(devcontainerCmd)
128+
}

internal/commands/init.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ Examples:
189189
return fmt.Errorf("failed to save configuration: %w", err)
190190
}
191191

192+
if projectConfig != nil && (templateFlag != "" || generateConfig) {
193+
fmt.Printf("📝 Generating lock file (devbox.lock.json)...\n")
194+
if err := WriteLockFileForProject(projectName, ""); err != nil {
195+
fmt.Printf("Warning: failed to write lock file: %v\n", err)
196+
}
197+
}
198+
192199
fmt.Printf("✅ Project '%s' initialized successfully!\n", projectName)
193200
fmt.Printf("📁 Workspace: %s\n", workspacePath)
194201
fmt.Printf("🐳 Box: %s\n", boxName)

0 commit comments

Comments
 (0)