feat: env add command supports the ".env" file for Bolt Framework apps#451
feat: env add command supports the ".env" file for Bolt Framework apps#451
env add command supports the ".env" file for Bolt Framework apps#451Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #451 +/- ##
==========================================
- Coverage 70.42% 70.42% -0.01%
==========================================
Files 221 221
Lines 18539 18606 +67
==========================================
+ Hits 13057 13103 +46
- Misses 4307 4322 +15
- Partials 1175 1181 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
zimeg
left a comment
There was a problem hiding this comment.
🗣️ Open ideas I still have that I'd like to share with kind reviewers-
| clients.IO.PrintTrace(ctx, slacktrace.EnvAddSuccess) | ||
| clients.IO.PrintInfo(ctx, false, "\n%s", style.Sectionf(style.TextSection{ | ||
| Emoji: "evergreen_tree", | ||
| Text: "App Environment", |
There was a problem hiding this comment.
🎨 note: We might follow up to change the section text for these env commands for outputs that match:
- 🌲 Environment Add
- 🌲 Environment List
- 🌲 Environment Remove
👾 ramble: I avoided this change for now because this PR is so large!
There was a problem hiding this comment.
praise: Good call! I like that this PR is keeping ROSI untouched, but we should make a note to follow-up on it adding parity between the two experiences.
| Emoji: "evergreen_tree", | ||
| Text: "App Environment", | ||
| Secondary: []string{ | ||
| fmt.Sprintf("Successfully added \"%s\" as a project environment variable", variableName), |
There was a problem hiding this comment.
📚 thought: This might behave in unexpected fashion for now since we prompt to select an app but save variables to the project, where all apps have access. I understand we have plans for more nuanced environment variable files and also that these .env file behaviors are understood when developing so I don't know if this requires more of a callout!
There was a problem hiding this comment.
Interesting, this is something that I overlooked when we first proposed the command changes.
suggestion(non-blocking): I'm happy with the current approach as phase 1, if you'd like to land the PR, avoid it becoming too large, and iterate.
However, I think we need to improve this experience. One of the main value props of the env add command is to set your app's third-party API Tokens. However, these tokens need to be set before you can run the app.
But... if you can't env add until you've created an app, then you're in a bit of a pickle. 🐣 You'd need to use the (currently) awkward install command before env add.
Would checking for the Deno runtime be a way to determine if we need to prompt for an app? If it's Deno then we prompt (local = .env, deploy = API), otherwise we skip the prompt and just use .env.
There was a problem hiding this comment.
@mwbrooks Thanks for calling out this awkward experience 👾 🔭
I'm confident we can decide to prompt for Deno apps and not Bolt apps, but we might want to follow up to mirror this in the env list command.
I was hesitant earlier to avoid blocking possible follow ups requiring app selection, but perhaps those explorations make app selection optional! 🚢
| // If the file does not exist, create it with the new entry. | ||
| if existing == nil { | ||
| return writeFile(fs, []byte(newEntry+"\n")) | ||
| } |
There was a problem hiding this comment.
🔬 question: From the command logic, would it be meaningful to output a note that the .env file shouldn't be checked into version control? Should we check if it's ignored already?
There was a problem hiding this comment.
Good question. It feels like the CLI is overstepping to add the .env to the .gitignore but perhaps that's just my own feeling. Others may consider that a delightful feature.
I think outputting a message is a very good start and allows us to improve upon it later (e.g. editing the .gitignore).
This approach has worked well for us in the past:
- Output the manual steps
- Let it bake and gauge how it feels
- Automate the manual steps
env command supports reading/writing to ".env" for Bolt Framework apps
env command supports reading/writing to ".env" for Bolt Framework appsenv add command supports adding/updating the ".env" for Bolt Framework apps
env add command supports adding/updating the ".env" for Bolt Framework appsenv add command supports the ".env" file for Bolt Framework apps
mwbrooks
left a comment
There was a problem hiding this comment.
✅ Thanks so much for updating the env add command to support .env files for Bolt apps! Woo! 🎉 I'm throwing an approval on this, but I had 2 important asks for improvement that can happen in this PR or as a follow-up.
🧪 The basics work, but I found a few edge-cases that we will want to fix. See my comments below.
💬 I understand this is a complex PR and thankful that you're keeping it scoped small! 🙇🏻 I found a few edge-cases that I don't think we want to bring into production. However, it's your call if you'd like to merge this PR and iterate on the edge-cases or do it inside this PR.
- Skip App Prompt for Non-Deno Projects: We have a chicken-and-egg 🐣 scenario where the
env addcommand fails when there are no apps. But, the happy path experience is to set your third-party API Tokens as environment variables before creating your first app withrun. We'll want to explore loosening up this command for non-ROSI (or non-Deno, which may be the clue) apps. 🧩 - Flexible
.envFile Format: Right now, the.envupdate logic is quite strict and results in duplicating existing entries. Since the.envfile is a user-edited file, we know there will be quirky formatting. It looks like other dotenv packages have a more flexible parsing, so we will want to try to match it. We may need to break out some string match foo. 🥷
| Short: "Add an environment variable to the app", | ||
| Long: strings.Join([]string{ | ||
| "Add an environment variable to an app deployed to Slack managed infrastructure.", | ||
| "Add an environment variable to the app.", |
There was a problem hiding this comment.
suggestion: Perhaps we should say "project" instead of "app"? For ROSI apps, it was actually the app (server-side) but for all other apps including locally run Deno apps, it's actually the "project's" .env file.
| "Add an environment variable to the app.", | |
| "Add an environment variable to the project.", |
| @@ -33,12 +34,15 @@ func NewEnvAddCommand(clients *shared.ClientFactory) *cobra.Command { | |||
| Use: "add <name> <value> [flags]", | |||
| Short: "Add an environment variable to the app", | |||
There was a problem hiding this comment.
suggestion(non-blocking): See the suggestion below. You may want to merge this change if you accept the one below.
| Short: "Add an environment variable to the app", | |
| Short: "Add an environment variable to the project", |
| @@ -33,12 +34,15 @@ func NewEnvAddCommand(clients *shared.ClientFactory) *cobra.Command { | |||
| Use: "add <name> <value> [flags]", | |||
There was a problem hiding this comment.
suggestion: Seems like a nice time to update the Usage to be more accurate?
| Use: "add <name> <value> [flags]", | |
| Use: "add <name> [value] [flags]", |
| "the \".env\" file. This includes the \"run\" command.", | ||
| "", | ||
| "The \"deploy\" command gathers environment variables from the \".env\" file as well", |
There was a problem hiding this comment.
suggestion(non-blocking): Please ignore if you want, but thought this may be a chance to keep the code simpler.
| "the \".env\" file. This includes the \"run\" command.", | |
| "", | |
| "The \"deploy\" command gathers environment variables from the \".env\" file as well", | |
| `the ".env" file. This includes the "run" command.`, | |
| "", | |
| `The "deploy" command gathers environment variables from the ".env" file as well`, |
| if clients.Config.ForceFlag { | ||
| return nil | ||
| } | ||
| return cmdutil.IsSlackHostedProject(ctx, clients) |
There was a problem hiding this comment.
praise: Bolt is coming for you env! ⚡
| ) | ||
| }, | ||
| }, | ||
| "add a variable to the .env file for non-hosted app": { |
There was a problem hiding this comment.
praise: Love seeing the new test use-case!
| // Set sets a single environment variable in the .env file, preserving | ||
| // comments, blank lines, and other formatting. If the key already exists its | ||
| // value is replaced in-place. Otherwise the entry is appended. The file is | ||
| // created if it does not exist. | ||
| func Set(fs afero.Fs, name string, value string) error { |
There was a problem hiding this comment.
praise: 🧑🍳 💋 👌🏻 What a great Godoc description!
| // If the file does not exist, create it with the new entry. | ||
| if existing == nil { | ||
| return writeFile(fs, []byte(newEntry+"\n")) | ||
| } |
There was a problem hiding this comment.
Good question. It feels like the CLI is overstepping to add the .env to the .gitignore but perhaps that's just my own feeling. Others may consider that a delightful feature.
I think outputting a message is a very good start and allows us to improve upon it later (e.g. editing the .gitignore).
This approach has worked well for us in the past:
- Output the manual steps
- Let it bake and gauge how it feels
- Automate the manual steps
| return err | ||
| } | ||
|
|
||
| // If the file does not exist, create it with the new entry. |
There was a problem hiding this comment.
question(non-blocking): This is out-of-scope of this PR, but our original discussion included a env init that would copy a .env.sample or .env.example to be .env.
If we eventually add this ability, I can see the init being called here instead of creating a blank .env, since the app may include a .env.sample or .env.example.
| // Try each possible form of the old entry, longest (most specific) first. | ||
| // The file can store multiline values with actual newlines, so also try | ||
| // the double-quoted raw form. | ||
| entries := []string{ | ||
| "export " + name + "=" + oldMarshaledValue, | ||
| "export " + name + "=" + `"` + oldValue + `"`, | ||
| "export " + name + "=" + oldValue, | ||
| "export " + name + "=" + "'" + oldValue + "'", | ||
| name + "=" + oldMarshaledValue, | ||
| name + "=" + `"` + oldValue + `"`, | ||
| name + "=" + oldValue, | ||
| name + "=" + "'" + oldValue + "'", | ||
| } |
There was a problem hiding this comment.
thought: This feels very brittle to me. 🤔
For example, the following format fails as a test case, but I'd expect it to be a valid entry - spaces around the equals sign, which works with Python's dotenv package:
FOO = "bar"Here is the test case:
"some poorly formatted .env file": {
name: "FOO",
value: "bar",
expectedFile: "FOO = \"bar\"\n",
},In production, if I use the above .env file and execute the env add then it duplicates the entries:
$ lack env add FOO banana
$ cat .env
FOO = "bar"
FOO="banana"I feel like there's a hesitation to use regex or string matches, but it may be the more flexible approach here.
Changelog
Summary
This PR uses the
env addcommand to add variables to the.envfile for non-hosted apps. Follows #437 🌲Preview
Reviewers
Attempt to write or update variables from this example and branch:
Notes
remotefunction runtime but this app selection might remain useful in later enhancements:Requirements