Conversation
EnvManager source developed in Gravatar-SDK-iOSEnvManager
Generated by 🚫 Danger |
--- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
It's an internal detail, not part of the public API. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Class methods delegate to a `@default` instance, so the existing `EnvManager.set_up` / `EnvManager.get_required_env!` API is preserved. This allows multiple independent configurations when needed and makes the class testable without global state. Also fixes the empty-value check to use the configurable `@print_error_lambda` instead of a hardcoded `UI.user_error!` call. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Covers initialization (path defaults, Dotenv loading), `get_required_env!` (happy path, three error branches, empty value), `require_env_vars!`, and class-level convenience methods. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Without this, the class isn't available to gem consumers. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
d58a39e to
e84c9b9
Compare
Consumers (and tests) had no way to clear the class-level `@default` instance. `reset!` enables proper teardown; `configured?` lets callers check state. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
`Dotenv.load` silently succeeds on missing files, deferring the error to `get_required_env!` time. An early warning helps developers notice misconfiguration sooner. Skipped on CI where .env files are never expected. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Same pattern as `print_error_lambda` — defaults to `UI.important` but can be injected. Tests now use a capturing lambda instead of mocking `FastlaneCore::UI`. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
`build_number`, `branch_name`, `commit_hash`, `pull_request_number`, and `pr_number_or_branch_name` wrap Buildkite env vars with sensible defaults and type coercion. Addresses AINFRA-829. See also https://github.com/Automattic/Gravatar-iOS/pull/9/files#r2161057223. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Callers building a key list dynamically will naturally have an array. Without `.flatten`, passing that array wraps it in the splat, iterating over one element (the array itself) instead of its contents. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
The local `EnvManager` copy is now provided by the release-toolkit gem. This validates the release-toolkit API against a real consumer. The `Gemfile` temporarily points to the `mokagio/env-manager` branch until wordpress-mobile/release-toolkit#578 is merged and released. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a new EnvManager utility to the release-toolkit Fastlane plugin to load .env files and provide clearer, context-aware environment variable access (including CI metadata helpers), along with an accompanying RSpec test suite.
Changes:
- Introduce
EnvManagerclass with instance API plus class-level convenience methods (set_up,get_required_env!,require_env_vars!,reset!,configured?). - Add comprehensive specs covering initialization behavior, required env var access, and CI helper methods.
- Update plugin autoload glob to include the new
env_managerdirectory and document the addition inCHANGELOG.md.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
lib/fastlane/plugin/wpmreleasetoolkit/env_manager/env_manager.rb |
Adds the EnvManager implementation (Dotenv loading, error/warning output injection, CI helpers, class-level delegation). |
spec/env_manager_spec.rb |
Adds specs validating EnvManager behavior and error messaging. |
lib/fastlane/plugin/wpmreleasetoolkit.rb |
Ensures the plugin loads Ruby files from the new env_manager directory. |
CHANGELOG.md |
Notes the new feature in Trunk. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # This preserves the existing API: `EnvManager.set_up(...)` then `EnvManager.get_required_env!(...)`. | ||
|
|
||
| def self.set_up(**args) | ||
| default_print_error_lambda.call('EnvManager is already configured. Call `EnvManager.reset!` before calling `EnvManager.set_up(...)` again.') if configured? |
There was a problem hiding this comment.
.set_up relies on default_print_error_lambda raising to prevent reconfiguration, but if a caller injects a non-raising lambda the method will continue and overwrite @default anyway. Make this guard deterministic by raising/returning immediately after emitting the error.
| default_print_error_lambda.call('EnvManager is already configured. Call `EnvManager.reset!` before calling `EnvManager.set_up(...)` again.') if configured? | |
| if configured? | |
| default_print_error_lambda.call('EnvManager is already configured. Call `EnvManager.reset!` before calling `EnvManager.set_up(...)` again.') | |
| return @default | |
| end |
| def self.default! | ||
| return @default if configured? | ||
|
|
||
| default_print_error_lambda.call('EnvManager is not configured. Call `EnvManager.set_up(...)` first.') |
There was a problem hiding this comment.
default! calls default_print_error_lambda but does not raise/return afterward; with a non-raising lambda this will return nil and callers will fail later with less clear errors. Consider raising a specific exception (or returning after calling the lambda) so the failure mode is consistent.
| default_print_error_lambda.call('EnvManager is not configured. Call `EnvManager.set_up(...)` first.') | |
| message = 'EnvManager is not configured. Call `EnvManager.set_up(...)` first.' | |
| default_print_error_lambda.call(message) | |
| raise RuntimeError, message |
| if running_on_ci? | ||
| @print_error_lambda.call(message) | ||
| elsif File.exist?(@env_path) | ||
| @print_error_lambda.call("#{message} Consider adding it to #{@env_path}.") | ||
| else | ||
| env_file_dir = File.dirname(@env_path) | ||
| env_file_name = File.basename(@env_path) | ||
|
|
||
| @print_error_lambda.call <<~MSG | ||
| #{env_file_name} not found in #{env_file_dir} while looking for env var #{key}. | ||
|
|
||
| Please copy #{@env_example_path} to #{@env_path} and fill in the value for #{key}. | ||
|
|
||
| mkdir -p #{env_file_dir} && cp #{@env_example_path} #{@env_path} | ||
| MSG | ||
| end | ||
| end | ||
|
|
||
| value = ENV.fetch(key) | ||
|
|
||
| @print_error_lambda.call("Env var for key #{key} is set but empty. Please set a value for #{key}.") if value.to_s.empty? |
There was a problem hiding this comment.
get_required_env! assumes @print_error_lambda will raise; otherwise execution continues to ENV.fetch(key) (raising KeyError) and the error message paths above are bypassed. To keep behavior consistent with the method’s bang semantics, explicitly raise (or return after a raising call) when the key is missing or the value is empty.
| if running_on_ci? | |
| @print_error_lambda.call(message) | |
| elsif File.exist?(@env_path) | |
| @print_error_lambda.call("#{message} Consider adding it to #{@env_path}.") | |
| else | |
| env_file_dir = File.dirname(@env_path) | |
| env_file_name = File.basename(@env_path) | |
| @print_error_lambda.call <<~MSG | |
| #{env_file_name} not found in #{env_file_dir} while looking for env var #{key}. | |
| Please copy #{@env_example_path} to #{@env_path} and fill in the value for #{key}. | |
| mkdir -p #{env_file_dir} && cp #{@env_example_path} #{@env_path} | |
| MSG | |
| end | |
| end | |
| value = ENV.fetch(key) | |
| @print_error_lambda.call("Env var for key #{key} is set but empty. Please set a value for #{key}.") if value.to_s.empty? | |
| error_message = | |
| if running_on_ci? | |
| message | |
| elsif File.exist?(@env_path) | |
| "#{message} Consider adding it to #{@env_path}." | |
| else | |
| env_file_dir = File.dirname(@env_path) | |
| env_file_name = File.basename(@env_path) | |
| <<~MSG | |
| #{env_file_name} not found in #{env_file_dir} while looking for env var #{key}. | |
| Please copy #{@env_example_path} to #{@env_path} and fill in the value for #{key}. | |
| mkdir -p #{env_file_dir} && cp #{@env_example_path} #{@env_path} | |
| MSG | |
| end | |
| @print_error_lambda.call(error_message) | |
| raise KeyError, error_message | |
| end | |
| value = ENV.fetch(key) | |
| if value.to_s.empty? | |
| message = "Env var for key #{key} is set but empty. Please set a value for #{key}." | |
| @print_error_lambda.call(message) | |
| raise ArgumentError, message | |
| end |
| @print_error_lambda.call <<~MSG | ||
| #{env_file_name} not found in #{env_file_dir} while looking for env var #{key}. | ||
|
|
||
| Please copy #{@env_example_path} to #{@env_path} and fill in the value for #{key}. | ||
|
|
||
| mkdir -p #{env_file_dir} && cp #{@env_example_path} #{@env_path} | ||
| MSG |
There was a problem hiding this comment.
The setup instructions include a shell command built from interpolated paths. If any path contains spaces or shell metacharacters, the copy/paste command can break or behave unexpectedly. Quote/escape the paths in the suggested command (e.g., via Shellwords.escape).
| expect(described_class.get_required_env!('CK1')).to eq('v1') | ||
| expect(described_class.get_required_env!('CK2')).to eq('v2') |
There was a problem hiding this comment.
This example is labeled as testing .require_env_vars! delegation, but it only calls .get_required_env! and never exercises .require_env_vars!. Update the expectation to call described_class.require_env_vars!(...) so the delegation is actually covered.
| expect(described_class.get_required_env!('CK1')).to eq('v1') | |
| expect(described_class.get_required_env!('CK2')).to eq('v2') | |
| expect { described_class.require_env_vars!('CK1', 'CK2') }.not_to raise_error |
| @@ -6,7 +6,7 @@ module Fastlane | |||
| module Wpmreleasetoolkit | |||
| # Return all .rb files inside the "actions", "helper" and "models" directories | |||
There was a problem hiding this comment.
The comment above all_classes says it returns files from only actions, helper, and models, but the glob also includes versioning and now env_manager. Update the comment to reflect the actual directories being loaded.
| # Return all .rb files inside the "actions", "helper" and "models" directories | |
| # Return all .rb files inside the "actions", "env_manager", "helper", "models", and "versioning" directories |
The guard relied on `default_print_error_lambda` raising to prevent overwriting `@default`. With a non-raising lambda, execution continued and silently replaced the configured instance. Return the existing instance after emitting the error so the guard works regardless of whether the lambda raises. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
`default!` relied on the error lambda raising to halt execution. With a non-raising lambda, it returned `nil`, causing confusing `NoMethodError`s downstream. Now explicitly raises after calling the lambda as a safety net. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Both the missing-key and empty-value branches relied on the error lambda raising to halt execution. With a non-raising lambda, the missing-key path fell through to `ENV.fetch` (less helpful `KeyError`), and the empty-value path silently returned the empty string. Now raises `KeyError` / `ArgumentError` after calling the lambda as a safety net. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
The test was calling `.get_required_env!` instead of `.require_env_vars!`, so the delegation was never exercised. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
--- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
Paths containing spaces or shell metacharacters broke the copy-paste `mkdir -p ... && cp ...` command in the setup instructions. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| def self.default_print_error_lambda | ||
| @default&.instance_variable_get(:@print_error_lambda) || @default_print_error_lambda || ->(message) { FastlaneCore::UI.user_error!(message) } |
`EnvManager` requires `dotenv` directly. It was only available transitively through `fastlane`, which would break the plugin if Fastlane ever dropped it. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A strict `ENV['CI'] == 'true'` check misses providers that set `CI=1` or other truthy values. Accept any non-empty value other than `false`/`0`. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces `instance_variable_get(:@print_error_lambda)` with a proper reader. Less brittle if the ivar is ever renamed. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ### New Features | ||
|
|
||
| _None_ | ||
| - Add `EnvManager` class for loading `.env` files and accessing required environment variables with user-friendly error messages. [#578] |
There was a problem hiding this comment.
I understand this won't necessarily break compatibility (unless it conflicts to top level EnvManager per repo, but we could fix that here as pointed out above).
But maybe we could still provide some migration instructions for repos, for the sake of clarity?
There was a problem hiding this comment.
Once EnvManager is namespaced (#3 / per discussion above), there is no class collision risk, so I think we can keep the migration nudge to a single sentence in the existing entry rather than a dedicated section. Updated in cbecee8 to Add Fastlane::Wpmreleasetoolkit::EnvManager … Repos that maintain their own EnvManager can switch to this centralized implementation. WDYT?
`Dotenv.load` mutates the process `ENV`, so multiple `EnvManager` instances pointing at different `.env` files would silently share state through a single global namespace and `reset!` couldn't undo it. Use `Dotenv.parse` instead and store the result in `@loaded_env`, then look up keys in process `ENV` first (preserving the no-override semantics of the original `Dotenv.load`) and fall back to the parsed file. Per Ian's suggestion in PR #578. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mention the fully qualified name so repos with a homegrown `EnvManager` know they can drop it in favour of the toolkit one without name clashes. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Matches the rest of the plugin's conventions and avoids colliding with the top-level `EnvManager` classes some consumer repos currently define. No top-level alias is introduced: the whole point of the move is to leave the global constant free. Per Copilot/Ian's suggestion in PR #578. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Copilot's suggestion in PR #578. Considered two options: A) Constrain to class + message: `raise_error(KeyError, /…/)`. B) Match by message regex only. Picked B as the less constrained option. The dedicated "raises KeyError even when the error lambda does not raise" spec already pins down the exception class contract, so re-checking it on every message variant just couples the test to an internal detail that a refactor might legitimately change. Note: the previous form, `raise_error("…")`, already matched any class with that exact message — Copilot's premise that it defaulted to `RuntimeError` is not quite right — so this change mainly relaxes the message match from exact-string to substring/pattern. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per Ian's suggestion in PR #578. The repo-URL-guessing TODO is a "let's make the lib smarter" wish we don't actually want. The Fastlane-decoupling TODO has sat there for a while without a real reason to act on it and the rationale comment beside it just contrasts against a TODO that's now gone. --- Generated with the help of Claude Code, https://claude.com/claude-code Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Thanks @iangmaia ! I think I addressed all your suggestions now. |
What does it do?
Adds
EnvManager, a helper class for loading.envfiles and accessing environment variables with clear error messages.Originally developed in the Gravatar-SDK-iOS repo, then adapted for the release toolkit.
Note
The diff is big, but it's 2/3 tests
.envfile via Dotenv at initializationget_required_env!(key)returns the value or raises with a context-aware message (CI vs. local, file exists vs. missing)require_env_vars!(*keys)validates multiple keys at once, failing fast on the first missing one — accepts both varargs and arrays.envfile is missing (suppressed on CI)set_up,get_required_env!,reset!,configured?) for backward compatibilitybuild_number,branch_name,commit_hash,pull_request_number,pr_number_or_branch_name)Example adoption
See Automattic/simplenote-ios#1742 for an example of migrating a project from a local
EnvManagercopy to the release-toolkit version.Checklist before requesting a review
bundle exec rubocopto test for code style violations and recommendationsspecs/*_spec.rb) if applicablebundle exec rspecto run the whole test suite and ensure all your tests passCHANGELOG.mdfile to describe your changes under the appropriate existing###subsection of the existing## Trunksection.MIGRATION.mdfile to describe how the changes will affect the migration from the previous major version and what the clients will need to change and consider.