env_check 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.env_check.yml +15 -0
- data/.rspec +3 -0
- data/.rubocop.yml +41 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +57 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/RAILS_COMPATIBILITY.md +36 -0
- data/README.md +384 -0
- data/bin/console +44 -0
- data/bin/env_check +265 -0
- data/bin/setup +26 -0
- data/lib/env_check/config.rb +85 -0
- data/lib/env_check/rake_task.rb +12 -0
- data/lib/env_check/validators.rb +101 -0
- data/lib/env_check/version.rb +5 -0
- data/lib/env_check.rb +152 -0
- data/lib/tasks/env_check.rake +16 -0
- metadata +212 -0
data/bin/console
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require "bundler/setup"
|
5
|
+
require "env_check"
|
6
|
+
|
7
|
+
# Load development environment if available
|
8
|
+
if File.exist?(".env")
|
9
|
+
require "dotenv"
|
10
|
+
Dotenv.load
|
11
|
+
end
|
12
|
+
|
13
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
14
|
+
# with your gem easier. You can also use a different console, if you like.
|
15
|
+
|
16
|
+
puts "🔍 EnvCheck Console v#{EnvCheck::VERSION}"
|
17
|
+
puts "Available methods:"
|
18
|
+
puts " EnvCheck.verify # Verify default config"
|
19
|
+
puts " EnvCheck.verify! # Verify with exception on failure"
|
20
|
+
puts " EnvCheck.verify(file) # Verify custom config file"
|
21
|
+
puts ""
|
22
|
+
|
23
|
+
# Set up some useful variables for console testing
|
24
|
+
config_path = EnvCheck::Config.discover_config_path
|
25
|
+
if File.exist?(config_path)
|
26
|
+
puts "📁 Found #{config_path}"
|
27
|
+
config_result = begin
|
28
|
+
EnvCheck.verify
|
29
|
+
rescue StandardError
|
30
|
+
nil
|
31
|
+
end
|
32
|
+
if config_result
|
33
|
+
puts "✅ Config loaded successfully"
|
34
|
+
else
|
35
|
+
puts "⚠️ Config has issues"
|
36
|
+
end
|
37
|
+
else
|
38
|
+
puts "📝 No config file found. Run EnvCheck init first."
|
39
|
+
end
|
40
|
+
|
41
|
+
puts ""
|
42
|
+
|
43
|
+
require "irb"
|
44
|
+
IRB.start(__FILE__)
|
data/bin/env_check
ADDED
@@ -0,0 +1,265 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
# bin/env_check - CLI for EnvCheck gem
|
5
|
+
|
6
|
+
require "optparse"
|
7
|
+
require "fileutils"
|
8
|
+
require_relative "../lib/env_check"
|
9
|
+
|
10
|
+
# Module for CLI output helpers
|
11
|
+
module CLIHelpers
|
12
|
+
def success(message)
|
13
|
+
puts message unless @options[:quiet]
|
14
|
+
end
|
15
|
+
|
16
|
+
def info(message)
|
17
|
+
puts message unless @options[:quiet]
|
18
|
+
end
|
19
|
+
|
20
|
+
def warning(message)
|
21
|
+
puts "⚠️ #{message}" unless @options[:quiet]
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(message)
|
25
|
+
warn message
|
26
|
+
end
|
27
|
+
|
28
|
+
def error_exit(message, code = 1)
|
29
|
+
warn "Error: #{message}"
|
30
|
+
exit code
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_config_path
|
34
|
+
Dir.exist?("config") ? "config/env_check.yml" : ".env_check.yml"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# CLI class for better organization
|
39
|
+
class EnvCheckCLI
|
40
|
+
include CLIHelpers
|
41
|
+
def initialize(args = ARGV)
|
42
|
+
@args = args
|
43
|
+
@options = {
|
44
|
+
config: nil, # Will be auto-discovered if not specified
|
45
|
+
quiet: false,
|
46
|
+
verbose: false
|
47
|
+
}
|
48
|
+
@command = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def run
|
52
|
+
parse_arguments
|
53
|
+
execute_command
|
54
|
+
rescue StandardError => e
|
55
|
+
error_exit(e.message.to_s)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def parse_arguments
|
61
|
+
parser = create_option_parser
|
62
|
+
parser.parse!(@args)
|
63
|
+
@command = @args.shift
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_option_parser
|
67
|
+
OptionParser.new do |opts|
|
68
|
+
configure_banner_and_commands(opts)
|
69
|
+
configure_options(opts)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def configure_banner_and_commands(opts)
|
74
|
+
opts.banner = "Usage: env_check [options] <command>"
|
75
|
+
opts.separator ""
|
76
|
+
opts.separator "Commands:"
|
77
|
+
opts.separator " init Create a new env_check.yml configuration file"
|
78
|
+
opts.separator " check Validate environment variables against configuration"
|
79
|
+
opts.separator " version Show version number"
|
80
|
+
opts.separator ""
|
81
|
+
opts.separator "Options:"
|
82
|
+
end
|
83
|
+
|
84
|
+
def configure_options(opts)
|
85
|
+
opts.on("-c", "--config PATH",
|
86
|
+
"Configuration file path (auto-discovered: .env_check.yml or config/env_check.yml)") do |path|
|
87
|
+
@options[:config] = path
|
88
|
+
end
|
89
|
+
|
90
|
+
opts.on("-q", "--quiet", "Suppress output (only show errors)") do
|
91
|
+
@options[:quiet] = true
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on("-v", "--verbose", "Show detailed output") do
|
95
|
+
@options[:verbose] = true
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on("-h", "--help", "Show this help message") do
|
99
|
+
puts opts
|
100
|
+
exit 0
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def execute_command
|
105
|
+
case @command
|
106
|
+
when "init"
|
107
|
+
init_command
|
108
|
+
when "check"
|
109
|
+
check_command
|
110
|
+
when "version"
|
111
|
+
version_command
|
112
|
+
when nil
|
113
|
+
show_help_and_exit
|
114
|
+
else
|
115
|
+
error_exit("Unknown command: #{@command}")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def init_command
|
120
|
+
path = @options[:config] || determine_init_path
|
121
|
+
|
122
|
+
begin
|
123
|
+
# Create directory if it doesn't exist
|
124
|
+
dir = File.dirname(path)
|
125
|
+
FileUtils.mkdir_p(dir) unless dir == "."
|
126
|
+
|
127
|
+
if File.exist?(path)
|
128
|
+
warning("Configuration file already exists: #{path}")
|
129
|
+
return
|
130
|
+
end
|
131
|
+
|
132
|
+
create_config_file(path)
|
133
|
+
success("Created configuration file: #{path}")
|
134
|
+
|
135
|
+
unless @options[:quiet]
|
136
|
+
puts "\nNext steps:"
|
137
|
+
puts "1. Edit #{path} to define your required environment variables"
|
138
|
+
puts "2. Run 'env_check check' to validate your environment"
|
139
|
+
end
|
140
|
+
rescue StandardError => e
|
141
|
+
error_exit("Failed to create configuration file: #{e.message}")
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def check_command
|
146
|
+
path = @options[:config] || EnvCheck::Config.discover_config_path
|
147
|
+
validate_config_exists(path)
|
148
|
+
|
149
|
+
info("Checking environment variables using: #{path}") if @options[:verbose]
|
150
|
+
|
151
|
+
result = perform_validation(path)
|
152
|
+
handle_validation_result(result)
|
153
|
+
end
|
154
|
+
|
155
|
+
def validate_config_exists(path)
|
156
|
+
return if File.exist?(path)
|
157
|
+
|
158
|
+
error_exit("Configuration file not found: #{path}. Run 'env_check init' first.")
|
159
|
+
end
|
160
|
+
|
161
|
+
def perform_validation(path)
|
162
|
+
EnvCheck.verify(path)
|
163
|
+
rescue EnvCheck::Error => e
|
164
|
+
error_exit("Validation failed: #{e.message}")
|
165
|
+
rescue StandardError => e
|
166
|
+
error_exit("Unexpected error: #{e.message}")
|
167
|
+
end
|
168
|
+
|
169
|
+
def handle_validation_result(result)
|
170
|
+
if result.success?
|
171
|
+
handle_success_result(result)
|
172
|
+
else
|
173
|
+
handle_failure_result(result)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def handle_success_result(result)
|
178
|
+
success("✅ All environment variables are valid!")
|
179
|
+
|
180
|
+
if @options[:verbose] && !result.valid_vars.empty?
|
181
|
+
puts "\nValid variables:"
|
182
|
+
result.valid_vars.each { |var| puts " ✅ #{var}" }
|
183
|
+
end
|
184
|
+
|
185
|
+
display_warnings(result.warnings) unless result.warnings.empty?
|
186
|
+
end
|
187
|
+
|
188
|
+
def handle_failure_result(result)
|
189
|
+
error("❌ Environment validation failed!")
|
190
|
+
|
191
|
+
display_errors(result.errors) unless result.errors.empty?
|
192
|
+
display_warnings(result.warnings) unless result.warnings.empty?
|
193
|
+
|
194
|
+
exit 1
|
195
|
+
end
|
196
|
+
|
197
|
+
def display_errors(errors)
|
198
|
+
puts "\nErrors:"
|
199
|
+
errors.each { |error| puts " ❌ #{error}" }
|
200
|
+
end
|
201
|
+
|
202
|
+
def display_warnings(warnings)
|
203
|
+
puts "\nWarnings:"
|
204
|
+
warnings.each { |warning| puts " ⚠️ #{warning}" }
|
205
|
+
end
|
206
|
+
|
207
|
+
def version_command
|
208
|
+
puts EnvCheck::VERSION
|
209
|
+
end
|
210
|
+
|
211
|
+
def create_config_file(path)
|
212
|
+
File.write(path, config_template)
|
213
|
+
end
|
214
|
+
|
215
|
+
def config_template
|
216
|
+
<<~YAML
|
217
|
+
# EnvCheck Configuration
|
218
|
+
# Configure required and optional environment variables for your application
|
219
|
+
|
220
|
+
# Required environment variables (must be present and non-empty)
|
221
|
+
required:
|
222
|
+
- DATABASE_URL
|
223
|
+
- SECRET_KEY_BASE
|
224
|
+
|
225
|
+
# Optional environment variables with type validation
|
226
|
+
optional:
|
227
|
+
DEBUG: boolean # true, false, 1, 0, yes, no, on, off (case-insensitive)
|
228
|
+
PORT: port # valid port number (1-65535)
|
229
|
+
API_URL: url # must start with http:// or https://
|
230
|
+
ADMIN_EMAIL: email # valid email format
|
231
|
+
LOG_LEVEL: string # any string value
|
232
|
+
RATE_LIMIT: float # floating point number
|
233
|
+
CONFIG_PATH: path # file or directory path
|
234
|
+
SETTINGS: json # valid JSON string
|
235
|
+
#{" "}
|
236
|
+
# You can also configure per-environment settings:
|
237
|
+
# development:
|
238
|
+
# required:
|
239
|
+
# - DATABASE_URL
|
240
|
+
# optional:
|
241
|
+
# DEBUG: boolean
|
242
|
+
##{" "}
|
243
|
+
# production:
|
244
|
+
# required:
|
245
|
+
# - DATABASE_URL
|
246
|
+
# - SECRET_KEY_BASE
|
247
|
+
# - RAILS_MASTER_KEY
|
248
|
+
# optional:
|
249
|
+
# REDIS_URL: url
|
250
|
+
YAML
|
251
|
+
end
|
252
|
+
|
253
|
+
def show_help_and_exit
|
254
|
+
puts create_option_parser.help
|
255
|
+
exit 1
|
256
|
+
end
|
257
|
+
|
258
|
+
# Determine the best path for init command
|
259
|
+
def determine_init_path
|
260
|
+
default_config_path
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Run the CLI
|
265
|
+
EnvCheckCLI.new.run if __FILE__ == $PROGRAM_NAME
|
data/bin/setup
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
set -euo pipefail
|
3
|
+
IFS=$'\n\t'
|
4
|
+
set -vx
|
5
|
+
|
6
|
+
echo "🔧 Setting up env_check development environment..."
|
7
|
+
|
8
|
+
# Install dependencies
|
9
|
+
bundle install
|
10
|
+
|
11
|
+
# Run any other automated setup that you need to do here
|
12
|
+
echo "✅ Running initial tests to verify setup..."
|
13
|
+
bundle exec rspec --format documentation
|
14
|
+
|
15
|
+
echo "🎯 Running RuboCop to check code style..."
|
16
|
+
bundle exec rubocop
|
17
|
+
|
18
|
+
echo ""
|
19
|
+
echo "🎉 Setup complete!"
|
20
|
+
echo ""
|
21
|
+
echo "Available commands:"
|
22
|
+
echo " ./bin/console # Start interactive console"
|
23
|
+
echo " ./bin/env_check # Run CLI tool"
|
24
|
+
echo " bundle exec rspec # Run tests"
|
25
|
+
echo " bundle exec rubocop # Check code style"
|
26
|
+
echo ""
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EnvCheck
|
4
|
+
# Configuration management for EnvCheck
|
5
|
+
class Config
|
6
|
+
# Priority order for auto-discovery
|
7
|
+
DEFAULT_PATHS = [
|
8
|
+
".env_check.yml", # Root level (simple/tool-focused projects)
|
9
|
+
"config/env_check.yml" # Rails convention (if config/ exists)
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
attr_reader :required_vars, :optional_vars, :config_path
|
13
|
+
|
14
|
+
def initialize(config_path = nil, environment = nil)
|
15
|
+
@config_path = config_path || ENV["ENV_CHECK_CONFIG"] || discover_config_path
|
16
|
+
@environment = environment || ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
17
|
+
load_config
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.from_hash(config_hash)
|
21
|
+
instance = allocate
|
22
|
+
instance.instance_variable_set(:@required_vars, config_hash["required"] || [])
|
23
|
+
instance.instance_variable_set(:@optional_vars,
|
24
|
+
instance.send(:normalize_optional_vars, config_hash["optional"] || {}))
|
25
|
+
instance.instance_variable_set(:@config_path, "inline")
|
26
|
+
instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid?
|
30
|
+
File.exist?(@config_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Discover config file using priority order
|
34
|
+
def self.discover_config_path
|
35
|
+
DEFAULT_PATHS.find { |path| File.exist?(path) } || DEFAULT_PATHS.first
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def discover_config_path
|
41
|
+
self.class.discover_config_path
|
42
|
+
end
|
43
|
+
|
44
|
+
def load_config
|
45
|
+
if File.exist?(@config_path)
|
46
|
+
config = YAML.load_file(@config_path)
|
47
|
+
|
48
|
+
# Support environment-specific configurations
|
49
|
+
env_config = config[@environment] || config
|
50
|
+
|
51
|
+
@required_vars = env_config["required"] || []
|
52
|
+
|
53
|
+
# Handle optional vars - support both hash and array formats
|
54
|
+
optional_config = env_config["optional"] || {}
|
55
|
+
@optional_vars = normalize_optional_vars(optional_config)
|
56
|
+
else
|
57
|
+
@required_vars = []
|
58
|
+
@optional_vars = {}
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Normalize optional vars to support both hash and array formats
|
63
|
+
# Hash format: { "VAR" => "type" }
|
64
|
+
# Array format: [{ "VAR" => "type" }, "SIMPLE_VAR"]
|
65
|
+
def normalize_optional_vars(optional_config)
|
66
|
+
case optional_config
|
67
|
+
when Hash
|
68
|
+
optional_config
|
69
|
+
when Array
|
70
|
+
result = {}
|
71
|
+
optional_config.each do |item|
|
72
|
+
case item
|
73
|
+
when Hash
|
74
|
+
result.merge!(item)
|
75
|
+
when String
|
76
|
+
result[item] = nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
result
|
80
|
+
else
|
81
|
+
{}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module EnvCheck
|
4
|
+
# Custom validator classes for different environment variable types
|
5
|
+
module Validators
|
6
|
+
class Base
|
7
|
+
def self.valid?(value)
|
8
|
+
raise NotImplementedError, "Subclasses must implement #valid?"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class Boolean < Base
|
13
|
+
VALID_VALUES = %w[true false 1 0 yes no on off].freeze
|
14
|
+
|
15
|
+
def self.valid?(value)
|
16
|
+
return false if value.nil? || value.strip.empty?
|
17
|
+
|
18
|
+
VALID_VALUES.include?(value.strip.downcase)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Integer < Base
|
23
|
+
def self.valid?(value)
|
24
|
+
return false if value.nil? || value.strip.empty?
|
25
|
+
|
26
|
+
value.strip.match?(/\A-?\d+\z/)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Float < Base
|
31
|
+
def self.valid?(value)
|
32
|
+
return false if value.nil? || value.strip.empty?
|
33
|
+
|
34
|
+
begin
|
35
|
+
Float(value.strip)
|
36
|
+
true
|
37
|
+
rescue ArgumentError
|
38
|
+
false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Url < Base
|
44
|
+
def self.valid?(value)
|
45
|
+
return false if value.nil? || value.strip.empty?
|
46
|
+
|
47
|
+
value.strip.match?(%r{\Ahttps?://\S+\z})
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Email < Base
|
52
|
+
def self.valid?(value)
|
53
|
+
return false if value.nil? || value.strip.empty?
|
54
|
+
|
55
|
+
value.strip.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Path < Base
|
60
|
+
def self.valid?(value)
|
61
|
+
return false if value.nil? || value.strip.empty?
|
62
|
+
|
63
|
+
# Basic path validation - should not contain null bytes and be reasonable length
|
64
|
+
path = value.strip
|
65
|
+
!path.include?("\0") && path.length.positive? && path.length < 1000
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class Port < Base
|
70
|
+
def self.valid?(value)
|
71
|
+
return false if value.nil? || value.strip.empty?
|
72
|
+
|
73
|
+
port = value.strip.to_i
|
74
|
+
port.positive? && port <= 65_535
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Enum < Base
|
79
|
+
def self.valid?(value, allowed_values = [])
|
80
|
+
return false if value.nil? || value.strip.empty?
|
81
|
+
return false if allowed_values.empty?
|
82
|
+
|
83
|
+
allowed_values.include?(value.strip)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class JsonString < Base
|
88
|
+
def self.valid?(value)
|
89
|
+
return false if value.nil? || value.strip.empty?
|
90
|
+
|
91
|
+
begin
|
92
|
+
require "json"
|
93
|
+
JSON.parse(value.strip)
|
94
|
+
true
|
95
|
+
rescue JSON::ParserError
|
96
|
+
false
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/lib/env_check.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# EnvCheck is a Ruby gem to validate presence and format of environment variables
|
4
|
+
# before your application boots or runs. Supports YAML configuration and type checking.
|
5
|
+
#
|
6
|
+
# Features:
|
7
|
+
# - Checks for required and optional environment variables
|
8
|
+
# - Validates types: boolean, integer, url, string (default)
|
9
|
+
# - Supports config via `ENV["ENV_CHECK_CONFIG"]`
|
10
|
+
# - Auto-loads `.env` file if present
|
11
|
+
|
12
|
+
require "yaml"
|
13
|
+
|
14
|
+
# Load version and rake task
|
15
|
+
require_relative "env_check/version"
|
16
|
+
require_relative "env_check/config"
|
17
|
+
require_relative "env_check/validators"
|
18
|
+
require_relative "env_check/rake_task" if defined?(Rake)
|
19
|
+
|
20
|
+
# Load .env automatically (if present)
|
21
|
+
if File.exist?(".env")
|
22
|
+
begin
|
23
|
+
require "dotenv"
|
24
|
+
Dotenv.load
|
25
|
+
rescue LoadError
|
26
|
+
warn "🔍 dotenv not installed — skipping .env loading"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# EnvCheck provides validation for environment variables using a YAML config file.
|
31
|
+
module EnvCheck
|
32
|
+
class Error < StandardError; end
|
33
|
+
|
34
|
+
class Result
|
35
|
+
attr_reader :errors, :warnings, :valid_vars
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@errors = []
|
39
|
+
@warnings = []
|
40
|
+
@valid_vars = []
|
41
|
+
end
|
42
|
+
|
43
|
+
def add_error(message)
|
44
|
+
@errors << message
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_warning(message)
|
48
|
+
@warnings << message
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_valid(var_name)
|
52
|
+
@valid_vars << var_name
|
53
|
+
end
|
54
|
+
|
55
|
+
def success?
|
56
|
+
@errors.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
def display_results
|
60
|
+
@valid_vars.each { |var| puts "✅ #{var} is set" }
|
61
|
+
@errors.each { |error| puts "❌ #{error}" }
|
62
|
+
@warnings.each { |warning| puts "⚠️ #{warning}" }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Verifies environment variables against a YAML config file.
|
67
|
+
#
|
68
|
+
# @param config_path [String] path to the config file (auto-discovered: .env_check.yml or config/env_check.yml)
|
69
|
+
# @param environment [String] environment to use for environment-specific configuration
|
70
|
+
# @return [Result] validation result object
|
71
|
+
def self.verify(config_path = nil, environment = nil)
|
72
|
+
config = Config.new(config_path, environment)
|
73
|
+
result = Result.new
|
74
|
+
|
75
|
+
unless config.valid?
|
76
|
+
puts "⚠️ Config file not found: #{config.config_path}"
|
77
|
+
return result
|
78
|
+
end
|
79
|
+
|
80
|
+
validate_required_vars(config.required_vars, result)
|
81
|
+
validate_optional_vars(config.optional_vars, result)
|
82
|
+
|
83
|
+
result.display_results
|
84
|
+
result
|
85
|
+
end
|
86
|
+
|
87
|
+
# Verify with inline configuration (useful for testing or programmatic use)
|
88
|
+
def self.verify_with_config(config_hash)
|
89
|
+
config = Config.from_hash(config_hash)
|
90
|
+
result = Result.new
|
91
|
+
|
92
|
+
validate_required_vars(config.required_vars, result)
|
93
|
+
validate_optional_vars(config.optional_vars, result)
|
94
|
+
|
95
|
+
result.display_results
|
96
|
+
result
|
97
|
+
end
|
98
|
+
|
99
|
+
# Legacy method for backward compatibility - raises on error
|
100
|
+
def self.verify!(config_path = nil, environment = nil)
|
101
|
+
result = verify(config_path, environment)
|
102
|
+
raise Error, "Environment validation failed" unless result.success?
|
103
|
+
|
104
|
+
result
|
105
|
+
end
|
106
|
+
|
107
|
+
private_class_method def self.validate_required_vars(required_vars, result)
|
108
|
+
required_vars.each do |var|
|
109
|
+
if ENV[var].nil? || ENV[var].strip.empty?
|
110
|
+
result.add_error("Missing required ENV: #{var}")
|
111
|
+
else
|
112
|
+
result.add_valid(var)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private_class_method def self.validate_optional_vars(optional_vars, result)
|
118
|
+
optional_vars.each do |var, type|
|
119
|
+
value = ENV.fetch(var, nil)
|
120
|
+
next unless value
|
121
|
+
|
122
|
+
if valid_type?(value, type)
|
123
|
+
result.add_valid(var)
|
124
|
+
else
|
125
|
+
result.add_warning("#{var} should be a #{type}, got '#{value}'")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private_class_method def self.valid_type?(value, type)
|
131
|
+
case type.to_s.downcase
|
132
|
+
when "boolean"
|
133
|
+
Validators::Boolean.valid?(value)
|
134
|
+
when "integer"
|
135
|
+
Validators::Integer.valid?(value)
|
136
|
+
when "float"
|
137
|
+
Validators::Float.valid?(value)
|
138
|
+
when "url"
|
139
|
+
Validators::Url.valid?(value)
|
140
|
+
when "email"
|
141
|
+
Validators::Email.valid?(value)
|
142
|
+
when "path"
|
143
|
+
Validators::Path.valid?(value)
|
144
|
+
when "port"
|
145
|
+
Validators::Port.valid?(value)
|
146
|
+
when "json"
|
147
|
+
Validators::JsonString.valid?(value)
|
148
|
+
else
|
149
|
+
true # Default to valid for unknown types
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# lib/tasks/env_check.rake
|
4
|
+
|
5
|
+
require_relative "../../lib/env_check"
|
6
|
+
if File.exist?(".env")
|
7
|
+
require "dotenv"
|
8
|
+
Dotenv.load
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :env do
|
12
|
+
desc "Check environment variable configuration"
|
13
|
+
task :check do
|
14
|
+
EnvCheck.verify!
|
15
|
+
end
|
16
|
+
end
|