Class: Belt::CLI::FrontendEnvMap

Inherits:
Object
  • Object
show all
Defined in:
lib/belt/cli/frontend_env_map.rb

Overview

Resolves frontend env vars from a declarative map (env name → terraform output).

Map file (optional), first match wins:

<frontend-path>/env.yml
<frontend-path>/env.yaml
.belt/frontend_env.yml          (only for the default `frontend/` dir)
.belt/frontend_env.yaml

Example:

VITE_API_URL: api_url
VITE_COGNITO_USER_POOL_ID: cognito_user_pool_id
REACT_APP_API_URL: api_url

No map → default { "VITE_API_URL" => "api_url" } (backwards compatible).

Constant Summary collapse

MAP_CANDIDATES =
[
  File.join('frontend', 'env.yml'),
  File.join('frontend', 'env.yaml'),
  File.join('.belt', 'frontend_env.yml'),
  File.join('.belt', 'frontend_env.yaml')
].freeze
DEFAULT_MAP =
{ 'VITE_API_URL' => 'api_url' }.freeze
DOTENV_PATH =
File.join('frontend', '.env')

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env_name, env_dir: nil, map_path: nil, frontend_path: 'frontend') ⇒ FrontendEnvMap

Returns a new instance of FrontendEnvMap.



37
38
39
40
41
42
43
44
# File 'lib/belt/cli/frontend_env_map.rb', line 37

def initialize(env_name, env_dir: nil, map_path: nil, frontend_path: 'frontend')
  @env_name = env_name
  @env_dir = env_dir || File.join('infrastructure', env_name)
  @frontend_path = frontend_path
  @map_path = map_path || self.class.find_map_path(frontend_path)
  @map = load_map
  @tf_cache = {}
end

Instance Attribute Details

#env_dirObject (readonly)

Returns the value of attribute env_dir.



35
36
37
# File 'lib/belt/cli/frontend_env_map.rb', line 35

def env_dir
  @env_dir
end

#env_nameObject (readonly)

Returns the value of attribute env_name.



35
36
37
# File 'lib/belt/cli/frontend_env_map.rb', line 35

def env_name
  @env_name
end

#frontend_pathObject (readonly)

Returns the value of attribute frontend_path.



35
36
37
# File 'lib/belt/cli/frontend_env_map.rb', line 35

def frontend_path
  @frontend_path
end

#mapObject (readonly)

Returns the value of attribute map.



35
36
37
# File 'lib/belt/cli/frontend_env_map.rb', line 35

def map
  @map
end

#map_pathObject (readonly)

Returns the value of attribute map_path.



35
36
37
# File 'lib/belt/cli/frontend_env_map.rb', line 35

def map_path
  @map_path
end

Class Method Details

.candidates_for(dir) ⇒ Object



50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/belt/cli/frontend_env_map.rb', line 50

def self.candidates_for(dir)
  list = [
    File.join(dir, 'env.yml'),
    File.join(dir, 'env.yaml')
  ]
  if dir == 'frontend'
    list += [
      File.join('.belt', 'frontend_env.yml'),
      File.join('.belt', 'frontend_env.yaml')
    ]
  end
  list
end

.find_map_path(dir = 'frontend') ⇒ Object



46
47
48
# File 'lib/belt/cli/frontend_env_map.rb', line 46

def self.find_map_path(dir = 'frontend')
  candidates_for(dir).find { |path| File.file?(path) }
end

Instance Method Details

#process_envObject

Hash of env_var => value suitable for process env (npm run build / vite). Skips keys whose terraform output is missing.



66
67
68
69
70
71
72
# File 'lib/belt/cli/frontend_env_map.rb', line 66

def process_env
  resolved = {}
  each_resolved do |env_key, value, _tf_output|
    resolved[env_key] = value if value
  end
  resolved
end

#using_default_map?Boolean

Returns:

  • (Boolean)


126
127
128
# File 'lib/belt/cli/frontend_env_map.rb', line 126

def using_default_map?
  @map_path.nil?
end

#write_dotenv!(path: nil) ⇒ Object

Smart-merge mapped keys into /.env.

  • Only overwrites keys present in the map
  • Preserves unknown keys, comments, and blank lines
  • Missing TF output → warn, do not clobber an existing value


78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/belt/cli/frontend_env_map.rb', line 78

def write_dotenv!(path: nil)
  path ||= File.join(@frontend_path, '.env')
  FileUtils.mkdir_p(File.dirname(path))

  existing_lines = File.file?(path) ? File.readlines(path, chomp: true) : []
  updates = {}
  missing = []

  each_resolved do |env_key, value, tf_output|
    if value
      updates[env_key] = value
    else
      missing << [env_key, tf_output]
    end
  end

  missing.each do |env_key, tf_output|
    puts "⚠️  terraform output '#{tf_output}' missing — leaving #{env_key} unchanged in #{path}"
  end

  written = {}
  new_lines = existing_lines.map do |line|
    key = dotenv_key(line)
    if key && updates.key?(key)
      written[key] = true
      format_dotenv_line(key, updates[key])
    else
      line
    end
  end

  updates.each do |key, value|
    next if written[key]

    new_lines << format_dotenv_line(key, value)
    written[key] = true
  end

  content = "#{new_lines.join("\n")}\n"
  File.write(path, content)

  {
    path: path,
    updated: written.keys.sort,
    missing: missing.map(&:first)
  }
end