diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b6ed10e..14335b6 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -5,7 +5,7 @@ before: - go mod tidy builds: - - main: ./cmd/envtpl + - main: . env: - CGO_ENABLED=0 goos: diff --git a/README.md b/README.md index 2f52abd..1ec8349 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,20 @@ A CLI tool for generating .env files from .env.template files via prompts and random values. +## Installation + +Using `curl`: + + ```sh + curl -sfL https://raw.githubusercontent.com/flexstack/envtpl/main/install.sh | sh + ``` + +Using `go install`: + +```sh +go install github.com/flexstack/envtpl +``` + ## Usage ```sh diff --git a/cmd/envtpl/main.go b/cmd/envtpl/main.go deleted file mode 100644 index 1224a2c..0000000 --- a/cmd/envtpl/main.go +++ /dev/null @@ -1,145 +0,0 @@ -package main - -import ( - "errors" - - "fmt" - "os" - "path/filepath" - - "github.com/flexstack/envtpl" - "github.com/joho/godotenv" - "github.com/pterm/pterm" - flag "github.com/spf13/pflag" -) - -func main() { - // The first argument is the path to the template file - var file string - flag.StringVarP(&file, "output", "o", ".env", "The path to output the .env file") - flag.Parse() - - path := flag.Arg(0) - if path == "" { - defaultPaths := []string{".env.template", ".env.example"} - for _, p := range defaultPaths { - if _, err := os.Stat(p); err == nil { - path = p - break - } - } - } - - // Check if the path is a directory - if fi, err := os.Stat(path); err != nil || fi.IsDir() { - defaultPaths := []string{".env.template", ".env.example"} - found := false - - for _, p := range defaultPaths { - maybePath := filepath.Join(path, p) - - if _, err := os.Stat(maybePath); err == nil { - path = maybePath - found = true - break - } - } - - if !found { - fmt.Println("No template file found") - os.Exit(1) - } - } - - if path == "" { - fmt.Println("No template file found") - os.Exit(1) - } - - // Parse the template file - envVars, err := envtpl.Parse(path) - if err != nil { - fmt.Println("Error parsing template file:", path) - os.Exit(1) - } - - env := make(map[string]string) - - // If the .env file already exists, merge the contents - if _, err = os.Stat(file); err == nil { - existing, err := godotenv.Read(file) - if err != nil { - fmt.Println("Error reading existing .env file:", err) - os.Exit(1) - } - - for key, value := range existing { - env[key] = value - } - } - - hasChanges := false - for _, envVar := range envVars { - if env[envVar.Key] != "" { - continue - } - - hasChanges = true - value, err := envVar.Value.Generate() - if err != nil { - fmt.Println("Error generating value for:", envVar.Key) - os.Exit(1) - } - switch envVar.Value.Type { - case envtpl.Text, envtpl.Password: - prompt := envVar.Key - v, _ := envVar.Value.Generate() - if p, ok := v.(string); ok && p != "" { - prompt = p - } - var result string - if envVar.Value.Type == envtpl.Password { - result, _ = pterm.DefaultInteractiveTextInput.WithMask("*").Show(prompt) - } else { - result, _ = pterm.DefaultInteractiveTextInput.Show(prompt) - } - env[envVar.Key] = result - - case envtpl.Enum: - rawEnum, err := envVar.Value.Generate() - if errors.Is(err, envtpl.ErrInvalidArg) { - fmt.Println("Error generating value for:", envVar.Key) - os.Exit(1) - } - enum, ok := rawEnum.([]string) - if !ok { - fmt.Println("Error generating value for:", envVar.Key) - os.Exit(1) - } - result, _ := pterm.DefaultInteractiveSelect.WithOptions(enum).WithFilter(false).Show(envVar.Key) - env[envVar.Key] = result - - default: - env[envVar.Key] = value.(string) - } - } - - if !hasChanges { - fmt.Println("No changes to .env file") - os.Exit(0) - } - - // Generate the .env - contents, err := godotenv.Marshal(env) - if err != nil { - fmt.Println("Error generating .env file:", err) - os.Exit(1) - } - - // Write the .env file - fmt.Println("Writing to:", file) - if err = os.WriteFile(file, []byte(contents), 0644); err != nil { - fmt.Println("Error writing .env file:", err) - os.Exit(1) - } -} diff --git a/go.mod b/go.mod index fd08168..f61dc2d 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/flexstack/envtpl go 1.23.1 require ( - github.com/c-bata/go-prompt v0.2.6 github.com/flexstack/uuid v1.0.0 github.com/joho/godotenv v1.5.1 ) @@ -22,11 +21,7 @@ require ( ) require ( - github.com/mattn/go-colorable v0.1.7 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mattn/go-tty v0.0.3 // indirect - github.com/pkg/term v1.2.0-beta.2 // indirect github.com/pterm/pterm v0.12.79 github.com/spf13/pflag v1.0.5 golang.org/x/sys v0.16.0 // indirect diff --git a/go.sum b/go.sum index 1d6f364..621adcd 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +atomicgo.dev/assert v0.0.2 h1:FiKeMiZSgRrZsPo9qn/7vmr7mCsh5SZyXY4YGYiYwrg= +atomicgo.dev/assert v0.0.2/go.mod h1:ut4NcI3QDdJtlmAxQULOmA13Gz6e2DWbSAS8RUOmNYQ= atomicgo.dev/cursor v0.2.0 h1:H6XN5alUJ52FZZUkI7AlJbUc1aW38GWZalpYRPpoPOw= atomicgo.dev/cursor v0.2.0/go.mod h1:Lr4ZJB3U7DfPPOkbH7/6TOtJ4vFGHlgj1nc+n900IpU= atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= @@ -11,12 +13,13 @@ github.com/MarvinJWendt/testza v0.2.10/go.mod h1:pd+VWsoGUiFtq+hRKSU1Bktnn+DMCSr github.com/MarvinJWendt/testza v0.2.12/go.mod h1:JOIegYyV7rX+7VZ9r77L/eH6CfJHHzXjB69adAhzZkI= github.com/MarvinJWendt/testza v0.3.0/go.mod h1:eFcL4I0idjtIx8P9C6KkAuLgATNKpX4/2oUqKc6bF2c= github.com/MarvinJWendt/testza v0.4.2/go.mod h1:mSdhXiKH8sg/gQehJ63bINcCKp7RtYewEjXsvsVUPbE= +github.com/MarvinJWendt/testza v0.5.2 h1:53KDo64C1z/h/d/stCYCPY69bt/OSwjq5KpFNwi+zB4= +github.com/MarvinJWendt/testza v0.5.2/go.mod h1:xu53QFE5sCdjtMCKk8YMQ2MnymimEctc4n3EjyIYvEY= github.com/atomicgo/cursor v0.0.1/go.mod h1:cBON2QmmrysudxNBFthvMtN32r3jxVRIvzkUiF/RuIk= -github.com/c-bata/go-prompt v0.2.6 h1:POP+nrHE+DfLYx370bedwNhsqmpCUynWPxuHi0C5vZI= -github.com/c-bata/go-prompt v0.2.6/go.mod h1:/LMAke8wD2FsNu9EXNdHxNLbd9MedkPnCdfpU9wwHfY= github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/flexstack/uuid v1.0.0 h1:iJCndNC/1NyRW5vEFu3aR7HmZLBT9fxEbEFgaFI6nZ4= github.com/flexstack/uuid v1.0.0/go.mod h1:Ssmzr+osP1+Ee+s6eXC9YIbUK3aZVtp8gT5bzmKf6K8= @@ -29,28 +32,17 @@ github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwA github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= +github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= -github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI= -github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0= -github.com/pkg/term v1.2.0-beta.2 h1:L3y/h2jkuBVFdWiJvNfYfKmzcCnILw7mJWm2JQuMppw= -github.com/pkg/term v1.2.0-beta.2/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pterm/pterm v0.12.27/go.mod h1:PhQ89w4i95rhgE+xedAoqous6K9X+r6aSOI2eFF7DZI= github.com/pterm/pterm v0.12.29/go.mod h1:WI3qxgvoQFFGKGjGnJR849gU0TsEOvKn5Q8LlY1U7lg= @@ -64,6 +56,7 @@ github.com/pterm/pterm v0.12.79/go.mod h1:1v/gzOF1N0FsjbgTHZ1wVycRkKiatFvJSJC4IG github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -71,12 +64,16 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= +golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -84,18 +81,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8= -golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -132,3 +120,5 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..d8ca543 --- /dev/null +++ b/install.sh @@ -0,0 +1,276 @@ +#!/usr/bin/env bash +set -euo pipefail + +platform=$(uname -ms) + +# Reset +Color_Off='' + +# Regular Colors +Red='' +Green='' +Dim='' # White + +# Bold +Bold_White='' +Bold_Green='' + +if [[ -t 1 ]]; then + # Reset + Color_Off='\033[0m' # Text Reset + + # Regular Colors + Red='\033[0;31m' # Red + Green='\033[0;32m' # Green + Dim='\033[0;2m' # White + + # Bold + Bold_Green='\033[1;32m' # Bold Green + Bold_White='\033[1m' # Bold White +fi + +error() { + echo -e "${Red}error${Color_Off}:" "$@" >&2 + exit 1 +} + +info() { + echo -e "${Dim}$@ ${Color_Off}" +} + +info_bold() { + echo -e "${Bold_White}$@ ${Color_Off}" +} + +success() { + echo -e "${Green}$@ ${Color_Off}" +} + +if [[ $# -gt 1 ]]; then + error 'Too many arguments, only 1 is allowed. It can be a specific tag of envtpl to install. (e.g. "v0.1.0")' +fi + +case $platform in +'Darwin x86_64') + target=darwin-x86_64 + ;; +'Darwin arm64') + target=darwin-arm64 + ;; +'Linux aarch64' | 'Linux arm64') + target=linux-arm64 + ;; +'MINGW64'*) + target=windows-x86_64 + ;; +'Linux x86_64' | *) + target=linux-x86_64 + ;; +esac + +if [[ $target = darwin-x86_64 ]]; then + # Is this process running in Rosetta? + # redirect stderr to devnull to avoid error message when not running in Rosetta + if [[ $(sysctl -n sysctl.proc_translated 2>/dev/null) = 1 ]]; then + target=darwin-arm64 + info "Your shell is running in Rosetta 2. Downloading envtpl for $target instead" + fi +fi + +GITHUB=${GITHUB-"https://github.com"} + +github_repo="$GITHUB/flexstack/envtpl" + +exe_name=envtpl +ext_name=.tar.gz + +if [[ $target = windows-x86_64 ]]; then + exe_name=$exe_name.exe + ext_name=.zip +fi + +if [[ $# = 0 ]]; then + bin_uri=$github_repo/releases/latest/download/envtpl-$target$ext_name +else + bin_uri=$github_repo/releases/download/$1/envtpl-$target$ext_name +fi + +install_env=BIN_INSTALL +bin_env=\$$install_env/bin + +install_dir=${!install_env:-$HOME/.flexstack} +bin_dir=$install_dir/bin +exe=$bin_dir/envtpl + +if [[ ! -d $bin_dir ]]; then + mkdir -p "$bin_dir" || + error "Failed to create install directory \"$bin_dir\"" +fi + +curl --fail --location --progress-bar --output "$exe$ext_name" "$bin_uri" || + error "Failed to download envtpl from \"$bin_uri\"" + +if [[ $ext_name = .zip ]]; then + unzip -oqd "$bin_dir" "$exe$ext_name" || + error 'Failed to extract envtpl' +else + tar xzf "$exe$ext_name" -C "$bin_dir" || + error 'Failed to extract envtpl' +fi + +mv "$bin_dir/$exe_name" "$exe" || + error 'Failed to move extracted envtpl to destination' + +chmod +x "$exe" || + error 'Failed to set permissions on envtpl executable' + +rm "$exe$ext_name" + +tildify() { + if [[ $1 = $HOME/* ]]; then + local replacement=\~/ + + echo "${1/$HOME\//$replacement}" + else + echo "$1" + fi +} + +success "envtpl was installed successfully to $Bold_Green$(tildify "$exe")" + +if command -v envtpl >/dev/null; then + echo "Run 'envtpl --help' to get started" + exit +fi + +refresh_command='' + +tilde_bin_dir=$(tildify "$bin_dir") +quoted_install_dir=\"${install_dir//\"/\\\"}\" + +if [[ $quoted_install_dir = \"$HOME/* ]]; then + quoted_install_dir=${quoted_install_dir/$HOME\//\$HOME/} +fi + +case $(basename "$SHELL") in +fish) + commands=( + "set --export $install_env $quoted_install_dir" + "set --export PATH $bin_env \$PATH" + ) + + fish_config=$HOME/.config/fish/config.fish + tilde_fish_config=$(tildify "$fish_config") + + if [[ -w $fish_config ]]; then + { + echo -e '\n# envtpl' + + for command in "${commands[@]}"; do + echo "$command" + done + } >>"$fish_config" + + info "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_fish_config\"" + + refresh_command="source $tilde_fish_config" + else + echo "Manually add the directory to $tilde_fish_config (or similar):" + + for command in "${commands[@]}"; do + info_bold " $command" + done + fi + ;; +zsh) + commands=( + "export $install_env=$quoted_install_dir" + "export PATH=\"$bin_env:\$PATH\"" + ) + + zsh_config=$HOME/.zshrc + tilde_zsh_config=$(tildify "$zsh_config") + + if [[ -w $zsh_config ]]; then + { + echo -e '\n# envtpl' + + for command in "${commands[@]}"; do + echo "$command" + done + } >>"$zsh_config" + + info "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_zsh_config\"" + + refresh_command="exec $SHELL" + else + echo "Manually add the directory to $tilde_zsh_config (or similar):" + + for command in "${commands[@]}"; do + info_bold " $command" + done + fi + ;; +bash) + commands=( + "export $install_env=$quoted_install_dir" + "export PATH=$bin_env:\$PATH" + ) + + bash_configs=( + "$HOME/.bashrc" + "$HOME/.bash_profile" + ) + + if [[ ${XDG_CONFIG_HOME:-} ]]; then + bash_configs+=( + "$XDG_CONFIG_HOME/.bash_profile" + "$XDG_CONFIG_HOME/.bashrc" + "$XDG_CONFIG_HOME/bash_profile" + "$XDG_CONFIG_HOME/bashrc" + ) + fi + + set_manually=true + for bash_config in "${bash_configs[@]}"; do + tilde_bash_config=$(tildify "$bash_config") + + if [[ -w $bash_config ]]; then + { + echo -e '\n# envtpl' + + for command in "${commands[@]}"; do + echo "$command" + done + } >>"$bash_config" + + info "Added \"$tilde_bin_dir\" to \$PATH in \"$tilde_bash_config\"" + + refresh_command="source $bash_config" + set_manually=false + break + fi + done + + if [[ $set_manually = true ]]; then + echo "Manually add the directory to $tilde_bash_config (or similar):" + + for command in "${commands[@]}"; do + info_bold " $command" + done + fi + ;; +*) + echo 'Manually add the directory to ~/.bashrc (or similar):' + info_bold " export $install_env=$quoted_install_dir" + info_bold " export PATH=\"$bin_env:\$PATH\"" + ;; +esac + +info "To get started, run:" + +if [[ $refresh_command ]]; then + info_bold " $refresh_command" +fi + +info_bold " envtpl --help" diff --git a/main.go b/main.go index 6d91914..d9004ae 100644 --- a/main.go +++ b/main.go @@ -1,10 +1,12 @@ -package envtpl +package main import ( "cmp" "errors" "fmt" "math/rand" + "os" + "path/filepath" "regexp" "sort" "strconv" @@ -13,8 +15,141 @@ import ( "github.com/flexstack/envtpl/pkg/nanoid" "github.com/flexstack/uuid" "github.com/joho/godotenv" + "github.com/pterm/pterm" + flag "github.com/spf13/pflag" ) +func main() { + // The first argument is the path to the template file + var file string + flag.StringVarP(&file, "output", "o", ".env", "The path to output the .env file") + flag.Parse() + + path := flag.Arg(0) + if path == "" { + defaultPaths := []string{".env.template", ".env.example"} + for _, p := range defaultPaths { + if _, err := os.Stat(p); err == nil { + path = p + break + } + } + } + + // Check if the path is a directory + if fi, err := os.Stat(path); err != nil || fi.IsDir() { + defaultPaths := []string{".env.template", ".env.example"} + found := false + + for _, p := range defaultPaths { + maybePath := filepath.Join(path, p) + + if _, err := os.Stat(maybePath); err == nil { + path = maybePath + found = true + break + } + } + + if !found { + fmt.Println("No template file found") + os.Exit(1) + } + } + + if path == "" { + fmt.Println("No template file found") + os.Exit(1) + } + + // Parse the template file + envVars, err := Parse(path) + if err != nil { + fmt.Println("Error parsing template file:", path) + os.Exit(1) + } + + env := make(map[string]string) + + // If the .env file already exists, merge the contents + if _, err = os.Stat(file); err == nil { + existing, err := godotenv.Read(file) + if err != nil { + fmt.Println("Error reading existing .env file:", err) + os.Exit(1) + } + + for key, value := range existing { + env[key] = value + } + } + + hasChanges := false + for _, envVar := range envVars { + if env[envVar.Key] != "" { + continue + } + + hasChanges = true + value, err := envVar.Value.Generate() + if err != nil { + fmt.Println("Error generating value for:", envVar.Key) + os.Exit(1) + } + switch envVar.Value.Type { + case Text, Password: + prompt := envVar.Key + v, _ := envVar.Value.Generate() + if p, ok := v.(string); ok && p != "" { + prompt = p + } + var result string + if envVar.Value.Type == Password { + result, _ = pterm.DefaultInteractiveTextInput.WithMask("*").Show(prompt) + } else { + result, _ = pterm.DefaultInteractiveTextInput.Show(prompt) + } + env[envVar.Key] = result + + case Enum: + rawEnum, err := envVar.Value.Generate() + if errors.Is(err, ErrInvalidArg) { + fmt.Println("Error generating value for:", envVar.Key) + os.Exit(1) + } + enum, ok := rawEnum.([]string) + if !ok { + fmt.Println("Error generating value for:", envVar.Key) + os.Exit(1) + } + result, _ := pterm.DefaultInteractiveSelect.WithOptions(enum).WithFilter(false).Show(envVar.Key) + env[envVar.Key] = result + + default: + env[envVar.Key] = value.(string) + } + } + + if !hasChanges { + fmt.Println("No changes to .env file") + os.Exit(0) + } + + // Generate the .env + contents, err := godotenv.Marshal(env) + if err != nil { + fmt.Println("Error generating .env file:", err) + os.Exit(1) + } + + // Write the .env file + fmt.Println("Writing to:", file) + if err = os.WriteFile(file, []byte(contents), 0644); err != nil { + fmt.Println("Error writing .env file:", err) + os.Exit(1) + } +} + func Parse(file string) ([]EnvVar, error) { env, err := godotenv.Read(file) if err != nil {