@@ -6,6 +6,7 @@ package boxcli
66import (
77 "cmp"
88 "fmt"
9+ "path/filepath"
910 "regexp"
1011
1112 "github.com/pkg/errors"
@@ -24,6 +25,7 @@ type generateCmdFlags struct {
2425 force bool
2526 printEnvrcContent bool
2627 rootUser bool
28+ envrcDir string // only used by generate direnv command
2729}
2830
2931type generateDockerfileCmdFlags struct {
@@ -151,6 +153,15 @@ func direnvCmd() *cobra.Command {
151153 // this command marks a flag as hidden. Error handling for it is not necessary.
152154 _ = command .Flags ().MarkHidden ("print-envrc" )
153155
156+ // --envrc-dir allows users to specify a directory where the .envrc file should be generated
157+ // separately from the devbox config directory. Without this flag, the .envrc file
158+ // will be generated in the same directory as the devbox config file (i.e., either the current
159+ // directory or the directory specified by --config). This is useful for users who want to keep
160+ // their .envrc and devbox config files in different locations.
161+ command .Flags ().StringVar (
162+ & flags .envrcDir , "envrc-dir" , "" , "path to directory where the .envrc file should be generated. " +
163+ "If not specified, the .envrc file will be generated in the current working directory." )
164+
154165 flags .config .register (command )
155166 return command
156167}
@@ -266,13 +277,32 @@ func runGenerateCmd(cmd *cobra.Command, flags *generateCmdFlags) error {
266277}
267278
268279func runGenerateDirenvCmd (cmd * cobra.Command , flags * generateCmdFlags ) error {
280+ // --print-envrc is used within the .envrc file and therefore doesn't make sense to also
281+ // use it with --envrc-dir, which specifies a directory where the .envrc file should be generated.
282+ if flags .printEnvrcContent && flags .envrcDir != "" {
283+ return usererr .New (
284+ "Cannot use --print-envrc with --envrc-dir. " +
285+ "Use --envrc-dir to specify the directory where the .envrc file should be generated." )
286+ }
287+
288+ // Determine the directories for .envrc and config
289+ configDir , envrcDir , err := determineDirenvDirs (flags .config .path , flags .envrcDir )
290+ if err != nil {
291+ return errors .WithStack (err )
292+ }
293+
294+ generateOpts := devopt.EnvrcOpts {
295+ EnvrcDir : envrcDir ,
296+ ConfigDir : configDir ,
297+ EnvFlags : devopt .EnvFlags (flags .envFlag ),
298+ }
299+
269300 if flags .printEnvrcContent {
270- return devbox .PrintEnvrcContent (
271- cmd .OutOrStdout (), devopt .EnvFlags (flags .envFlag ))
301+ return devbox .PrintEnvrcContent (cmd .OutOrStdout (), generateOpts )
272302 }
273303
274304 box , err := devbox .Open (& devopt.Opts {
275- Dir : flags . config . path ,
305+ Dir : filepath . Join ( envrcDir , configDir ) ,
276306 Environment : flags .config .environment ,
277307 Stderr : cmd .ErrOrStderr (),
278308 })
@@ -281,5 +311,33 @@ func runGenerateDirenvCmd(cmd *cobra.Command, flags *generateCmdFlags) error {
281311 }
282312
283313 return box .GenerateEnvrcFile (
284- cmd .Context (), flags .force , devopt .EnvFlags (flags .envFlag ))
314+ cmd .Context (), flags .force , generateOpts )
315+ }
316+
317+ // Returns cononical paths for configDir and envrcDir. Both locations are relative to the current
318+ // working directory when provided to this function. However, since the config file will ultimately
319+ // be relative to the .envrc file, we need to determine the relative path from envrcDir to configDir.
320+ func determineDirenvDirs (configDir , envrcDir string ) (string , string , error ) {
321+ // If envrcDir is specified, use it as the directory for .envrc and
322+ // then determine configDir relative to that.
323+ if envrcDir != "" {
324+ if configDir == "" {
325+ return "" , envrcDir , nil
326+ }
327+
328+ relativeConfigDir , err := filepath .Rel (envrcDir , configDir )
329+ if err != nil {
330+ return "" , "" , errors .Wrapf (err , "failed to determine relative path from %s to %s" , envrcDir , configDir )
331+ }
332+
333+ // If the relative path is ".", it means configDir is the same as envrcDir.
334+ if relativeConfigDir == "." {
335+ relativeConfigDir = ""
336+ }
337+
338+ return relativeConfigDir , envrcDir , nil
339+ }
340+ // If envrcDir is not specified, we will use the configDir as the location for .envrc. This is
341+ // for backward compatibility (prior to the --envrc-dir flag being introduced).
342+ return "" , configDir , nil
285343}
0 commit comments