Skip to content

Commit cba645a

Browse files
authored
[zeroconfig] Default nodejs to corepack enabled (#2396)
## Summary * Adds `DEVBOX_COREPACK_ENABLED: "1"` to nodejs detector. * Adds ability to set env programmatically on config. (This probably doesn't work with interspaced comments) ## How was it tested? * Unit tests * Manually created project with package.json and then did `devbox init --auto`
1 parent 14cfacd commit cba645a

File tree

11 files changed

+157
-4
lines changed

11 files changed

+157
-4
lines changed

internal/devconfig/configfile/ast.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,3 +425,32 @@ func (c *configAST) beforeComment(path ...any) []byte {
425425
}),
426426
)
427427
}
428+
429+
func (c *configAST) createMemberIfMissing(key string) *hujson.ObjectMember {
430+
i := c.memberIndex(c.root.Value.(*hujson.Object), key)
431+
if i == -1 {
432+
c.root.Value.(*hujson.Object).Members = append(c.root.Value.(*hujson.Object).Members, hujson.ObjectMember{
433+
Name: hujson.Value{Value: hujson.String(key)},
434+
})
435+
i = len(c.root.Value.(*hujson.Object).Members) - 1
436+
}
437+
return &c.root.Value.(*hujson.Object).Members[i]
438+
}
439+
440+
func mapToObjectMembers(env map[string]string) []hujson.ObjectMember {
441+
members := make([]hujson.ObjectMember, 0, len(env))
442+
for k, v := range env {
443+
members = append(members, hujson.ObjectMember{
444+
Name: hujson.Value{Value: hujson.String(k)},
445+
Value: hujson.Value{Value: hujson.String(v)},
446+
})
447+
}
448+
return members
449+
}
450+
451+
func (c *configAST) setEnv(env map[string]string) {
452+
c.createMemberIfMissing("env").Value.Value = &hujson.Object{
453+
Members: mapToObjectMembers(env),
454+
}
455+
c.root.Format()
456+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package configfile
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/tailscale/hujson"
8+
)
9+
10+
func TestSetEnv(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
initial string
14+
env map[string]string
15+
expected string
16+
}{
17+
{
18+
name: "add env to empty config",
19+
initial: "{}",
20+
env: map[string]string{
21+
"FOO": "bar",
22+
"BAZ": "qux",
23+
},
24+
expected: `{"env": {"FOO": "bar", "BAZ": "qux"}}
25+
`,
26+
},
27+
{
28+
name: "update existing env",
29+
initial: `{
30+
"env": {
31+
"EXISTING": "value"
32+
}
33+
}`,
34+
env: map[string]string{
35+
"FOO": "bar",
36+
},
37+
expected: `{
38+
"env": {"FOO": "bar"}
39+
}
40+
`,
41+
},
42+
{
43+
name: "clear env with empty map",
44+
initial: `{
45+
"env": {
46+
"EXISTING": "value"
47+
}
48+
}`,
49+
env: map[string]string{},
50+
expected: `{
51+
"env": {}
52+
}
53+
`,
54+
},
55+
}
56+
57+
for _, tt := range tests {
58+
t.Run(tt.name, func(t *testing.T) {
59+
val, err := hujson.Parse([]byte(tt.initial))
60+
assert.NoError(t, err)
61+
62+
ast := &configAST{root: val}
63+
ast.setEnv(tt.env)
64+
65+
actual := string(ast.root.Pack())
66+
assert.Equal(t, tt.expected, actual)
67+
})
68+
}
69+
}

internal/devconfig/configfile/env.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,8 @@ func (c *ConfigFile) ParseEnvsFromDotEnv() (map[string]string, error) {
4444

4545
return envMap, nil
4646
}
47+
48+
func (c *ConfigFile) SetEnv(env map[string]string) {
49+
c.Env = env
50+
c.ast.setEnv(env)
51+
}

pkg/autodetect/autodetect.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ func populateConfig(ctx context.Context, path string, config *devconfig.Config)
3636
for _, pkg := range pkgs {
3737
config.PackageMutator().Add(pkg)
3838
}
39+
env, err := env(ctx, path)
40+
if err != nil {
41+
return err
42+
}
43+
config.Root.SetEnv(env)
3944
return nil
4045
}
4146

@@ -57,6 +62,14 @@ func packages(ctx context.Context, path string) ([]string, error) {
5762
return mostRelevantDetector.Packages(ctx)
5863
}
5964

65+
func env(ctx context.Context, path string) (map[string]string, error) {
66+
mostRelevantDetector, err := relevantDetector(path)
67+
if err != nil || mostRelevantDetector == nil {
68+
return nil, err
69+
}
70+
return mostRelevantDetector.Env(ctx)
71+
}
72+
6073
// relevantDetector returns the most relevant detector for the given path.
6174
// We could modify this to return a list of detectors and their scores or
6275
// possibly grouped detectors by category (e.g. python, server, etc.)

pkg/autodetect/detector/detector.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ import "context"
55
type Detector interface {
66
Relevance(path string) (float64, error)
77
Packages(ctx context.Context) ([]string, error)
8+
Env(ctx context.Context) (map[string]string, error)
89
}

pkg/autodetect/detector/go.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ func (d *GoDetector) Packages(ctx context.Context) ([]string, error) {
3838
return []string{"go@" + goVersion}, nil
3939
}
4040

41+
func (d *GoDetector) Env(ctx context.Context) (map[string]string, error) {
42+
return map[string]string{}, nil
43+
}
44+
4145
func parseGoVersion(goModContent string) string {
4246
// Use a regular expression to find the Go version directive
4347
re := regexp.MustCompile(`(?m)^go\s+(\d+\.\d+(\.\d+)?)`)

pkg/autodetect/detector/nodejs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ func (d *NodeJSDetector) Packages(ctx context.Context) ([]string, error) {
4141
return []string{"nodejs@" + d.nodeVersion(ctx)}, nil
4242
}
4343

44+
func (d *NodeJSDetector) Env(ctx context.Context) (map[string]string, error) {
45+
return map[string]string{"DEVBOX_COREPACK_ENABLED": "1"}, nil
46+
}
47+
4448
func (d *NodeJSDetector) nodeVersion(ctx context.Context) string {
4549
if d.packageJSON == nil || d.packageJSON.Engines.Node == "" {
4650
return "latest" // Default to latest if not specified

pkg/autodetect/detector/nodejs_test.go

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func TestNodeJSDetector_Relevance(t *testing.T) {
1717
fs fstest.MapFS
1818
expected float64
1919
expectedPackages []string
20+
expectedEnv map[string]string
2021
}{
2122
{
2223
name: "package.json in root",
@@ -27,6 +28,7 @@ func TestNodeJSDetector_Relevance(t *testing.T) {
2728
},
2829
expected: 1,
2930
expectedPackages: []string{"nodejs@latest"},
31+
expectedEnv: map[string]string{"DEVBOX_COREPACK_ENABLED": "1"},
3032
},
3133
{
3234
name: "package.json with node version",
@@ -41,6 +43,7 @@ func TestNodeJSDetector_Relevance(t *testing.T) {
4143
},
4244
expected: 1,
4345
expectedPackages: []string{"[email protected]"},
46+
expectedEnv: map[string]string{"DEVBOX_COREPACK_ENABLED": "1"},
4447
},
4548
{
4649
name: "no nodejs files",
@@ -54,12 +57,14 @@ func TestNodeJSDetector_Relevance(t *testing.T) {
5457
},
5558
expected: 0,
5659
expectedPackages: []string{},
60+
expectedEnv: map[string]string{},
5761
},
5862
{
5963
name: "empty directory",
6064
fs: fstest.MapFS{},
6165
expected: 0,
6266
expectedPackages: []string{},
67+
expectedEnv: map[string]string{},
6368
},
6469
}
6570

@@ -74,17 +79,21 @@ func TestNodeJSDetector_Relevance(t *testing.T) {
7479
require.NoError(t, err)
7580
}
7681

77-
d := &NodeJSDetector{Root: dir}
78-
err := d.Init()
82+
detector := &NodeJSDetector{Root: dir}
83+
err := detector.Init()
7984
require.NoError(t, err)
8085

81-
score, err := d.Relevance(dir)
86+
score, err := detector.Relevance(dir)
8287
require.NoError(t, err)
8388
assert.Equal(t, curTest.expected, score)
8489
if score > 0 {
85-
packages, err := d.Packages(context.Background())
90+
packages, err := detector.Packages(context.Background())
8691
require.NoError(t, err)
8792
assert.Equal(t, curTest.expectedPackages, packages)
93+
94+
env, err := detector.Env(context.Background())
95+
require.NoError(t, err)
96+
assert.Equal(t, curTest.expectedEnv, env)
8897
}
8998
})
9099
}
@@ -96,3 +105,10 @@ func TestNodeJSDetector_Packages(t *testing.T) {
96105
require.NoError(t, err)
97106
assert.Equal(t, []string{"nodejs@latest"}, packages)
98107
}
108+
109+
func TestNodeJSDetector_Env(t *testing.T) {
110+
d := &NodeJSDetector{}
111+
env, err := d.Env(context.Background())
112+
require.NoError(t, err)
113+
assert.Equal(t, map[string]string{"DEVBOX_COREPACK_ENABLED": "1"}, env)
114+
}

pkg/autodetect/detector/php.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ func (d *PHPDetector) Packages(ctx context.Context) ([]string, error) {
4949
return packages, nil
5050
}
5151

52+
func (d *PHPDetector) Env(ctx context.Context) (map[string]string, error) {
53+
return map[string]string{}, nil
54+
}
55+
5256
func (d *PHPDetector) phpVersion(ctx context.Context) string {
5357
require := d.composerJSON.Require
5458

pkg/autodetect/detector/poetry.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ func (d *PoetryDetector) Packages(ctx context.Context) ([]string, error) {
5858
return []string{"python@" + pythonVersion, "poetry@" + poetryVersion}, nil
5959
}
6060

61+
func (d *PoetryDetector) Env(ctx context.Context) (map[string]string, error) {
62+
return d.PythonDetector.Env(ctx)
63+
}
64+
6165
func determineBestVersion(ctx context.Context, pkg, version string) string {
6266
if version == "" {
6367
return "latest"

0 commit comments

Comments
 (0)