1
1
// Copyright 2023 Jetpack Technologies Inc and contributors. All rights reserved.
2
2
// Use of this source code is governed by the license in the LICENSE file.
3
3
4
- package impl
4
+ package devconfig
5
5
6
6
import (
7
7
"io"
8
8
"net/http"
9
9
"net/url"
10
- "os"
11
10
"path/filepath"
12
11
"regexp"
13
12
"strings"
@@ -16,12 +15,12 @@ import (
16
15
17
16
"go.jetpack.io/devbox/internal/boxcli/usererr"
18
17
"go.jetpack.io/devbox/internal/cuecfg"
19
- "go.jetpack.io/devbox/internal/debug"
20
- "go.jetpack.io/devbox/internal/fileutil"
21
18
"go.jetpack.io/devbox/internal/impl/shellcmd"
22
19
"go.jetpack.io/devbox/internal/planner/plansdk"
23
20
)
24
21
22
+ const DefaultName = "devbox.json"
23
+
25
24
// Config defines a devbox environment as JSON.
26
25
type Config struct {
27
26
// Packages is the slice of Nix packages that devbox makes available in
@@ -59,8 +58,9 @@ type Stage struct {
59
58
Command string `cue:"string" json:"command"`
60
59
}
61
60
62
- func defaultConfig () * Config {
61
+ func DefaultConfig () * Config {
63
62
return & Config {
63
+ Packages : []string {}, // initialize to empty slice instead of nil for consistent marshalling
64
64
Shell : & shellConfig {
65
65
Scripts : map [string ]* shellcmd.Commands {
66
66
"test" : {
@@ -80,6 +80,12 @@ func (c *Config) Hash() (string, error) {
80
80
return cuecfg .Hash (c )
81
81
}
82
82
83
+ func (c * Config ) Equals (other * Config ) bool {
84
+ hash1 , _ := c .Hash ()
85
+ hash2 , _ := other .Hash ()
86
+ return hash1 == hash2
87
+ }
88
+
83
89
func (c * Config ) NixPkgsCommitHash () string {
84
90
if c == nil || c .Nixpkgs == nil || c .Nixpkgs .Commit == "" {
85
91
return plansdk .DefaultNixpkgsCommit
@@ -101,21 +107,27 @@ func (c *Config) InitHook() *shellcmd.Commands {
101
107
return c .Shell .InitHook
102
108
}
103
109
110
+ // SaveTo writes the config to a file.
111
+ func (c * Config ) SaveTo (path string ) error {
112
+ cfgPath := filepath .Join (path , DefaultName )
113
+ return cuecfg .WriteFile (cfgPath , c )
114
+ }
115
+
104
116
func readConfig (path string ) (* Config , error ) {
105
117
cfg := & Config {}
106
118
return cfg , errors .WithStack (cuecfg .ParseFile (path , cfg ))
107
119
}
108
120
109
- // ReadConfig reads a devbox config file, and validates it.
110
- func ReadConfig (path string ) (* Config , error ) {
121
+ // Load reads a devbox config file, and validates it.
122
+ func Load (path string ) (* Config , error ) {
111
123
cfg , err := readConfig (path )
112
124
if err != nil {
113
125
return nil , err
114
126
}
115
127
return cfg , validateConfig (cfg )
116
128
}
117
129
118
- func readConfigFromURL (url * url.URL ) (* Config , error ) {
130
+ func LoadConfigFromURL (url * url.URL ) (* Config , error ) {
119
131
res , err := http .Get (url .String ())
120
132
if err != nil {
121
133
return nil , errors .WithStack (err )
@@ -130,7 +142,10 @@ func readConfigFromURL(url *url.URL) (*Config, error) {
130
142
if ! cuecfg .IsSupportedExtension (ext ) {
131
143
ext = ".json"
132
144
}
133
- return cfg , cuecfg .Unmarshal (data , ext , cfg )
145
+ if err = cuecfg .Unmarshal (data , ext , cfg ); err != nil {
146
+ return nil , errors .WithStack (err )
147
+ }
148
+ return cfg , validateConfig (cfg )
134
149
}
135
150
136
151
// WriteConfig saves a devbox config file.
@@ -142,96 +157,9 @@ func WriteConfig(path string, cfg *Config) error {
142
157
return cuecfg .WriteFile (path , cfg )
143
158
}
144
159
145
- // findProjectDir walks up the directory tree looking for a devbox.json
146
- // and upon finding it, will return the directory-path.
147
- //
148
- // If it doesn't find any devbox.json, then an error is returned.
149
- func findProjectDir (path string ) (string , error ) {
150
- debug .Log ("findProjectDir: path is %s\n " , path )
151
-
152
- // Sanitize the directory and use the absolute path as canonical form
153
- absPath , err := filepath .Abs (path )
154
- if err != nil {
155
- return "" , errors .WithStack (err )
156
- }
157
-
158
- // If the path is specified, then we check directly for a config.
159
- // Otherwise, we search the parent directories.
160
- if path != "" {
161
- return findProjectDirAtPath (absPath )
162
- }
163
- return findProjectDirFromParentDirSearch ("/" /*root*/ , absPath )
164
- }
165
-
166
- func findProjectDirAtPath (absPath string ) (string , error ) {
167
- fi , err := os .Stat (absPath )
168
- if err != nil {
169
- return "" , err
170
- }
171
-
172
- switch mode := fi .Mode (); {
173
- case mode .IsDir ():
174
- if ! fileutil .Exists (filepath .Join (absPath , configFilename )) {
175
- return "" , missingConfigError (absPath , false /*didCheckParents*/ )
176
- }
177
- return absPath , nil
178
- default : // assumes 'file' i.e. mode.IsRegular()
179
- if ! fileutil .Exists (filepath .Clean (absPath )) {
180
- return "" , missingConfigError (absPath , false /*didCheckParents*/ )
181
- }
182
- // we return a directory from this function
183
- return filepath .Dir (absPath ), nil
184
- }
185
- }
186
-
187
- func findProjectDirFromParentDirSearch (root string , absPath string ) (string , error ) {
188
- cur := absPath
189
- // Search parent directories for a devbox.json
190
- for cur != root {
191
- debug .Log ("finding %s in dir: %s\n " , configFilename , cur )
192
- if fileutil .Exists (filepath .Join (cur , configFilename )) {
193
- return cur , nil
194
- }
195
- cur = filepath .Dir (cur )
196
- }
197
- if fileutil .Exists (filepath .Join (cur , configFilename )) {
198
- return cur , nil
199
- }
200
- return "" , missingConfigError (absPath , true /*didCheckParents*/ )
201
- }
202
-
203
- func missingConfigError (path string , didCheckParents bool ) error {
204
- var workingDir string
205
- wd , err := os .Getwd ()
206
- if err == nil {
207
- workingDir = wd
208
- }
209
- // We try to prettify the `path` before printing
210
- if path == "." || path == "" || workingDir == path {
211
- path = "this directory"
212
- } else {
213
- // Instead of a long absolute directory, print the relative directory
214
-
215
- // if an error occurs, then just use `path`
216
- if workingDir != "" {
217
- relDir , err := filepath .Rel (workingDir , path )
218
- if err == nil {
219
- path = relDir
220
- }
221
- }
222
- }
223
-
224
- parentDirCheckAddendum := ""
225
- if didCheckParents {
226
- parentDirCheckAddendum = ", or any parent directories"
227
- }
228
-
229
- return usererr .New ("No devbox.json found in %s%s. Did you run `devbox init` yet?" , path , parentDirCheckAddendum )
230
- }
231
-
232
160
func validateConfig (cfg * Config ) error {
233
161
fns := []func (cfg * Config ) error {
234
- validateNixpkg ,
162
+ ValidateNixpkg ,
235
163
validateScripts ,
236
164
}
237
165
@@ -252,16 +180,18 @@ func validateScripts(cfg *Config) error {
252
180
return errors .New ("cannot have script with empty name in devbox.json" )
253
181
}
254
182
if whitespace .MatchString (k ) {
255
- return errors .Errorf ("cannot have script name with whitespace in devbox.json: %s" , k )
183
+ return errors .Errorf (
184
+ "cannot have script name with whitespace in devbox.json: %s" , k )
256
185
}
257
186
if strings .TrimSpace (scripts [k ].String ()) == "" {
258
- return errors .Errorf ("cannot have an empty script body in devbox.json: %s" , k )
187
+ return errors .Errorf (
188
+ "cannot have an empty script body in devbox.json: %s" , k )
259
189
}
260
190
}
261
191
return nil
262
192
}
263
193
264
- func validateNixpkg (cfg * Config ) error {
194
+ func ValidateNixpkg (cfg * Config ) error {
265
195
hash := cfg .NixPkgsCommitHash ()
266
196
if hash == "" {
267
197
return nil
0 commit comments