capistrano-node-dotenv 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fd4e85e20f0d755f17df29ea83a5e1de85c2b79431e55fb88aaa8a0b40b4d96a
4
+ data.tar.gz: 406b1bd86cd41345fd1c3521712465cc7441eb6c6b963dffd920fc55a024a0d3
5
+ SHA512:
6
+ metadata.gz: 52723bdc7a45285bb5ec6599fd0f63cbdcdab0140a27580699a36ee8e0f4445161bcf86ba218f770383f2936b978799fdb4019f328b334552fc7b15bee920ee6
7
+ data.tar.gz: cf4705e7b70b3bf1d339b3db871302b386ea43be90ac11c09ac096116cbd6ccf45cf8da6a91a730339c739e00e167cdd1e1cbe34811a69ba6dd880c3d446013e
@@ -0,0 +1,25 @@
1
+ name: Release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: write
9
+ id-token: write
10
+
11
+ jobs:
12
+ publish:
13
+ name: Push gem to RubyGems.org
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - name: Set up Ruby
20
+ uses: ruby/setup-ruby@v1
21
+ with:
22
+ ruby-version: '3.3'
23
+ bundler-cache: true
24
+
25
+ - uses: rubygems/release-gem@v1
data/.gitignore ADDED
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.gem
data/.rubocop.yml ADDED
@@ -0,0 +1,88 @@
1
+ # RuboCop configuration for ms-graph-mailer gem
2
+
3
+ AllCops:
4
+ NewCops: enable
5
+ TargetRubyVersion: 3.1
6
+ SuggestExtensions: false
7
+ Exclude:
8
+ - 'vendor/**/*'
9
+ - 'tmp/**/*'
10
+ - 'bin/**/*'
11
+
12
+ # Metrics
13
+
14
+ Metrics/BlockLength:
15
+ Max: 50
16
+ Exclude:
17
+ - 'spec/**/*'
18
+ - '*.gemspec'
19
+
20
+ Metrics/ClassLength:
21
+ Max: 150
22
+
23
+ Metrics/MethodLength:
24
+ Max: 40
25
+ Exclude:
26
+ - 'spec/**/*'
27
+
28
+ Metrics/AbcSize:
29
+ Max: 65
30
+ Exclude:
31
+ - 'spec/**/*'
32
+
33
+ Metrics/CyclomaticComplexity:
34
+ Max: 10
35
+
36
+ Metrics/PerceivedComplexity:
37
+ Max: 10
38
+
39
+ # Style
40
+
41
+ Style/Documentation:
42
+ Enabled: false
43
+
44
+ Style/StringLiterals:
45
+ EnforcedStyle: single_quotes
46
+
47
+ Style/StringLiteralsInInterpolation:
48
+ EnforcedStyle: single_quotes
49
+
50
+ Style/FrozenStringLiteralComment:
51
+ Enabled: true
52
+ EnforcedStyle: always
53
+
54
+ Style/TrailingCommaInArrayLiteral:
55
+ EnforcedStyleForMultiline: no_comma
56
+
57
+ Style/TrailingCommaInHashLiteral:
58
+ EnforcedStyleForMultiline: no_comma
59
+
60
+ Style/TrailingCommaInArguments:
61
+ EnforcedStyleForMultiline: no_comma
62
+
63
+ # Layout
64
+
65
+ Layout/LineLength:
66
+ Max: 150
67
+ AllowedPatterns:
68
+ - '\s+# '
69
+
70
+ Layout/EmptyLinesAroundAttributeAccessor:
71
+ Enabled: true
72
+
73
+ Layout/SpaceAroundMethodCallOperator:
74
+ Enabled: true
75
+
76
+ # Lint
77
+
78
+ Gemspec/DevelopmentDependencies:
79
+ Enabled: false
80
+
81
+ Gemspec/RequireMFA:
82
+ Enabled: false
83
+
84
+ Lint/RaiseException:
85
+ Enabled: true
86
+
87
+ Lint/StructNewOverride:
88
+ Enabled: true
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.3.5
data/CHANGELOG.md ADDED
@@ -0,0 +1,25 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-03-02
9
+
10
+ ### Added
11
+
12
+ - Initial release
13
+ - `dotenv:setup` – upload local `config/application.yml` to the remote shared folder as a `.env` file
14
+ - `dotenv:update` – interactively compare local config with remote `.env` and push selected changes
15
+ - `dotenv:get` – interactively compare remote `.env` with local config and pull selected changes
16
+ - `dotenv:init` – bootstrap local `config/application.yml` by pulling `.env` files from all stages
17
+ - `dotenv:show` – print the content of the remote `.env` file for the current stage
18
+ - `dotenv:backup` – create a timestamped backup of the remote `.env` file (retains latest 5)
19
+ - `dotenv:rollback` – restore the remote `.env` from the most recent backup
20
+ - `dotenv:check` – pre-deploy checks (file exists, not git-tracked, stage config present)
21
+ - Automatic symlinking of the remote `.env` via `deploy:started` hook
22
+ - Section-marker format in generated `.env` files for deterministic parsing (`# Begin/End <stage> envs`)
23
+ - `Parser` module for reading and writing section-annotated `.env` files
24
+ - `Helpers` module with backup management, hash diffing, and interactive prompts
25
+ - `Paths` module for resolving local and remote file paths via Capistrano `fetch`
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in capistrano-ops.gemspec
6
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 zauberware
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,235 @@
1
+ # capistrano-node-dotenv
2
+
3
+ A [Capistrano](https://capistranorb.com/) plugin for managing `.env` files on remote servers running Node.js applications. It handles uploading, syncing, backing up, and rolling back the `.env` file that lives in your deployment's shared folder.
4
+
5
+ ## Table of Contents
6
+
7
+ <details>
8
+ <summary>Click to expand</summary>
9
+
10
+ - [How it works](#how-it-works)
11
+ - [Installation](#installation)
12
+ - [Configuration](#configuration)
13
+ - [Tasks](#tasks)
14
+ - [dotenv:setup](#dotenvsetup)
15
+ - [dotenv:update](#dotenvupdate)
16
+ - [dotenv:get](#dotenvget)
17
+ - [dotenv:init](#dotenvinit)
18
+ - [dotenv:show](#dotenvshow)
19
+ - [dotenv:backup](#dotenvbackup)
20
+ - [dotenv:rollback](#dotenvrollback)
21
+ - [dotenv:check](#dotenvcheck)
22
+ - [Automatic symlinking](#automatic-symlinking)
23
+ - [Backup behaviour](#backup-behaviour)
24
+ - [Requirements](#requirements)
25
+ - [Contributing](#contributing)
26
+ - [License](#license)
27
+
28
+ </details>
29
+
30
+ ## How it works
31
+
32
+ The gem stores your environment configuration locally in `config/application.yml` — a YAML file where top-level keys are global variables and nested keys are stage-specific variables. On deploy, the relevant keys are extracted, assembled into a proper `.env` file, and uploaded to the server's shared folder. The `.env` file is automatically added to Capistrano's `linked_files` so it gets symlinked into each release.
33
+
34
+ ### Local config format (`config/application.yml`)
35
+
36
+ ```yaml
37
+ # Global variables shared across all stages
38
+ DATABASE_URL: postgres://localhost/myapp
39
+
40
+ # Stage-specific variables
41
+ production:
42
+ NODE_ENV: production
43
+ API_KEY: secret-prod
44
+
45
+ staging:
46
+ NODE_ENV: staging
47
+ API_KEY: secret-staging
48
+ ```
49
+
50
+ ### Remote `.env` file format
51
+
52
+ The generated `.env` file uses section markers so the gem can parse it back:
53
+
54
+ ```
55
+ # Begin global envs 2026-03-02 10:00:00
56
+ DATABASE_URL=postgres://localhost/myapp
57
+ # End global envs 2026-03-02 10:00:00
58
+ # Begin production envs 2026-03-02 10:00:00
59
+ NODE_ENV=production
60
+ API_KEY=secret-prod
61
+ # End production envs 2026-03-02 10:00:00
62
+ ```
63
+
64
+ [↑](#)
65
+
66
+ ## Installation
67
+
68
+ Add to your `Gemfile`:
69
+
70
+ ```ruby
71
+ gem 'capistrano-node-dotenv'
72
+ ```
73
+
74
+ Then run:
75
+
76
+ ```bash
77
+ bundle install
78
+ ```
79
+
80
+ Require the gem in your `Capfile`:
81
+
82
+ ```ruby
83
+ require 'capistrano/node/dotenv'
84
+ ```
85
+
86
+ Make sure `config/application.yml` is **not** tracked by git:
87
+
88
+ ```bash
89
+ git rm --cached config/application.yml
90
+ echo 'config/application.yml' >> .gitignore
91
+ ```
92
+
93
+ [↑](#)
94
+
95
+ ## Configuration
96
+
97
+ Set these variables in `config/deploy.rb` or a stage file. All have sensible defaults.
98
+
99
+ | Variable | Default | Description |
100
+ | ---------------------- | ------------------------------------- | -------------------------------------------------------- |
101
+ | `dotenv_local_path` | `config/application.yml` | Path to the local YAML config file |
102
+ | `dotenv_remote_path` | `.env` | Path to the `.env` file relative to the shared folder |
103
+ | `dotenv_env` | `fetch(:node_env) \|\| fetch(:stage)` | The environment/stage key to extract from the local YAML |
104
+ | `dotenv_remote_backup` | `false` | Whether to automatically back up on deploy |
105
+
106
+ Example:
107
+
108
+ ```ruby
109
+ # config/deploy.rb
110
+ set :dotenv_local_path, 'config/application.yml'
111
+ set :dotenv_remote_path, '.env'
112
+ set :dotenv_env, -> { fetch(:stage) }
113
+ ```
114
+
115
+ [↑](#)
116
+
117
+ ## Tasks
118
+
119
+ ```
120
+ cap dotenv:setup # Upload local config/application.yml to the server (first-time or full overwrite)
121
+ cap dotenv:update # Compare local vs remote .env interactively and push selected changes
122
+ cap dotenv:get # Compare remote vs local .env and optionally pull remote values into local
123
+ cap dotenv:init # Build local config/application.yml by pulling .env files from all stages
124
+ cap dotenv:show # Print the content of the remote .env file for the current stage
125
+ cap dotenv:backup # Create a timestamped backup of the remote .env file (keeps latest 5)
126
+ cap dotenv:rollback # Roll back the remote .env file to the most recent backup
127
+ cap dotenv:check # Run all pre-deploy checks (file exists, not git-tracked, stage config present)
128
+ ```
129
+
130
+ ### dotenv:setup
131
+
132
+ Reads `config/application.yml`, extracts global keys and stage-specific keys, generates a `.env` file with section markers, and uploads it to `shared/<dotenv_remote_path>`. Use this the first time you deploy or when you want to fully overwrite the remote file.
133
+
134
+ ```bash
135
+ cap production dotenv:setup
136
+ ```
137
+
138
+ ### dotenv:update
139
+
140
+ Compares your local `config/application.yml` with the current remote `.env`. Displays any differences and interactively asks whether to overwrite the remote globals and/or stage section. Automatically creates a backup before writing.
141
+
142
+ ```bash
143
+ cap production dotenv:update
144
+ ```
145
+
146
+ ### dotenv:get
147
+
148
+ The reverse of `dotenv:update`. Compares the remote `.env` with your local file and interactively asks whether to pull remote values back into `config/application.yml`.
149
+
150
+ ```bash
151
+ cap production dotenv:get
152
+ ```
153
+
154
+ ### dotenv:init
155
+
156
+ Iterates over all configured Capistrano stages, downloads each stage's `.env` file, and merges everything into a local `config/application.yml`. Useful for bootstrapping a fresh local environment from existing servers.
157
+
158
+ ```bash
159
+ cap production dotenv:init
160
+ ```
161
+
162
+ ### dotenv:show
163
+
164
+ Prints the raw content of the remote `.env` file to stdout.
165
+
166
+ ```bash
167
+ cap production dotenv:show
168
+ ```
169
+
170
+ ### dotenv:backup
171
+
172
+ Creates a timestamped backup (e.g. `.env-2026-03-02-10-00-00.bak`) in the shared folder. Automatically removes older backups, keeping only the 5 most recent.
173
+
174
+ ```bash
175
+ cap production dotenv:backup
176
+ ```
177
+
178
+ ### dotenv:rollback
179
+
180
+ Restores the remote `.env` from the most recent `.bak` file. Does nothing if no backup exists.
181
+
182
+ ```bash
183
+ cap production dotenv:rollback
184
+ ```
185
+
186
+ ### dotenv:check
187
+
188
+ Runs three pre-deploy checks in sequence:
189
+
190
+ 1. **`check_dotenv_file_exists`** – Verifies that `config/application.yml` exists locally.
191
+ 2. **`check_git_tracking`** – Ensures the file is not accidentally committed to git.
192
+ 3. **`check_config_present`** – Confirms that the current stage's config is not empty.
193
+
194
+ ```bash
195
+ cap production dotenv:check
196
+ ```
197
+
198
+ [↑](#)
199
+
200
+ ## Automatic symlinking
201
+
202
+ The gem hooks into `deploy:started` and automatically adds the remote `.env` path to `linked_files`. No manual configuration in `deploy.rb` is needed — the file will be symlinked into every release directory.
203
+
204
+ [↑](#)
205
+
206
+ ## Backup behaviour
207
+
208
+ - Backups are named `<filename>-YYYY-MM-DD-HH-MM-SS.bak` and stored next to the `.env` in the shared folder.
209
+ - At most 5 backups are retained; older ones are deleted automatically.
210
+ - `dotenv:update` always creates a backup before writing to the remote file.
211
+
212
+ [↑](#)
213
+
214
+ ## Requirements
215
+
216
+ - Ruby `>= 3.1.4, < 3.4.0`
217
+ - Capistrano 3.x
218
+
219
+ [↑](#)
220
+
221
+ ## Contributing
222
+
223
+ 1. Fork it ( https://github.com/zauberware/capistrano-node-dotenv/fork )
224
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
225
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
226
+ 4. Push to the branch (`git push origin my-new-feature`)
227
+ 5. Create a new Pull Request
228
+
229
+ [↑](#)
230
+
231
+ ## License
232
+
233
+ MIT — see [LICENSE.txt](LICENSE.txt).
234
+
235
+ [↑](#)
data/Rakefile ADDED
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
9
+
10
+ namespace :version do
11
+ desc 'Bump patch version (0.1.0 -> 0.1.1)'
12
+ task :patch do
13
+ ruby 'scripts/bump_version.rb patch'
14
+ end
15
+
16
+ desc 'Bump minor version (0.1.0 -> 0.2.0)'
17
+ task :minor do
18
+ ruby 'scripts/bump_version.rb minor'
19
+ end
20
+
21
+ desc 'Bump major version (0.1.0 -> 1.0.0)'
22
+ task :major do
23
+ ruby 'scripts/bump_version.rb major'
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+
5
+ require 'capistrano/node/dotenv/version'
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = 'capistrano-node-dotenv'
9
+ s.version = Capistrano::Node::Dotenv::VERSION
10
+ s.platform = Gem::Platform::RUBY
11
+ s.authors = ['Florian Crusius']
12
+ s.email = ['florian@zauberware.com']
13
+ s.license = 'MIT'
14
+ s.homepage = 'https://github.com/zauberware/capistrano-node-dotenv'
15
+ s.summary = 'dotenv tasks for node applications'
16
+ s.description = 'A collection of dotenv tasks for node applications'
17
+ s.files = `git ls-files`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
19
+ s.require_paths = ['lib']
20
+
21
+ s.required_ruby_version = '>= 3.1.4', '< 3.4.0'
22
+ s.add_development_dependency 'bundler', '~> 2.0'
23
+ s.add_development_dependency 'rake', '~> 13.0'
24
+ s.add_development_dependency 'rspec', '~> 3.0'
25
+ s.add_development_dependency 'rubocop', '~> 1.0'
26
+ s.add_development_dependency 'rubocop-rake', '~> 0.6'
27
+ s.add_development_dependency 'rubocop-rspec', '~> 2.0'
28
+ end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Capistrano
4
+ module Node
5
+ module Dotenv
6
+ module Helpers
7
+ def remote_file_exists?
8
+ test("[ -f #{dotenv_remote_path} ]")
9
+ end
10
+
11
+ def remote_backup_exists?
12
+ count_remote_files.positive?
13
+ end
14
+
15
+ def rollback_remote_backup
16
+ latest_backup = latest_remote_backup
17
+ puts "mv #{dotenv_remote_path.parent.join(latest_backup)} #{dotenv_remote_path}"
18
+ execute :mv, dotenv_remote_path.parent.join(latest_backup), dotenv_remote_path
19
+ execute :ls, '-la', dotenv_remote_path.parent
20
+ end
21
+
22
+ def latest_remote_backup
23
+ command = "ls -a1t #{dotenv_remote_path.parent} | grep -E '#{backup_regex}' | head -n 1"
24
+ capture(command).strip
25
+ end
26
+
27
+ def backup_regex
28
+ # filename we are looking for "#{dotenv_remote_path.basename}-yyyy-mm-dd-HH-MM-SS.bak"
29
+ "#{dotenv_remote_path.basename}-[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}-[0-9]{2}.bak"
30
+ end
31
+
32
+ def count_remote_files
33
+ command = "ls -a1 #{dotenv_remote_path.parent} | grep -E '#{backup_regex}' | wc -l"
34
+ capture(command).to_i
35
+ end
36
+
37
+ def create_remote_backup
38
+ backup_file = "#{dotenv_remote_path.basename}-#{Time.now.strftime('%Y-%m-%d-%H-%M-%S')}.bak"
39
+ # puts "cp #{dotenv_remote_path} #{dotenv_remote_path.parent.join(backup_file)}"
40
+ execute :cp, dotenv_remote_path, dotenv_remote_path.parent.join(backup_file)
41
+ end
42
+
43
+ def cleanup_remote_backups
44
+ number_of_backups = count_remote_files
45
+ return unless number_of_backups > 5
46
+
47
+ diff = number_of_backups - 5
48
+ # remove older backups and keep the latest 5
49
+ command = "ls -a1t #{dotenv_remote_path.parent} | grep -E '#{backup_regex}' | tail -n #{diff}"
50
+
51
+ capture(command).split("\n").each do |file|
52
+ execute "rm #{dotenv_remote_path.parent.join(file)}"
53
+ end
54
+ end
55
+
56
+ def local_dotenv(_env)
57
+ @local_dotenv ||= YAML.load(ERB.new(File.read(dotenv_local_path)).result)
58
+
59
+ @local_dotenv || {}
60
+ end
61
+
62
+ def local_yaml
63
+ YAML.safe_load_file(dotenv_local_path) || {}
64
+ end
65
+
66
+ def dotenv_env
67
+ fetch(:dotenv_env).to_s
68
+ end
69
+
70
+ def dotenv_content
71
+ local_dotenv(dotenv_env).to_yaml
72
+ end
73
+
74
+ def configs(yaml, env)
75
+ env_str = env.to_s
76
+ stage_yml = yaml[env_str]&.sort.to_h
77
+ global_yml = remove_nested(yaml)&.sort.to_h
78
+
79
+ other_stages_yml = stages.each_with_object({}) do |f, hash|
80
+ f_str = f.to_s
81
+ next if f_str == env_str
82
+
83
+ hash[f_str] = yaml[f_str]&.sort.to_h
84
+ end.compact
85
+
86
+ [global_yml, stage_yml, other_stages_yml]
87
+ end
88
+
89
+ def remove_nested(hash)
90
+ hash.each_with_object({}) do |(key, value), new_hash|
91
+ new_hash[key] = value unless value.is_a?(Hash)
92
+ end
93
+ end
94
+
95
+ def sort_with_nested(hash)
96
+ hash.each_with_object({}) do |(key, value), new_hash|
97
+ new_hash[key] = value.is_a?(Hash) ? sort_with_nested(value) : value
98
+ end.sort.to_h
99
+ end
100
+
101
+ def compare_hashes(hash1, hash2)
102
+ all_keys = hash1.keys | hash2.keys # Union of all keys from both hashes
103
+ all_keys.each_with_object({}) do |key, changes_hash|
104
+ old_value = hash2[key].nil? ? 'nil' : hash2[key].to_s
105
+ new_value = hash1[key].nil? ? 'nil' : hash1[key].to_s
106
+
107
+ changes_hash[key] = { old: old_value, new: new_value } if old_value != new_value
108
+ end.tap { |changes| return changes.empty? ? nil : changes }
109
+ end
110
+
111
+ # selection helpers
112
+ def ask_to_overwrite(question)
113
+ answer = ''
114
+ until %w[y n].include?(answer)
115
+ print "#{question}? (y/N): "
116
+ answer = $stdin.gets.strip.downcase
117
+ end
118
+ answer == 'y'
119
+ end
120
+
121
+ # info helpers
122
+
123
+ def print_changes(changes, message)
124
+ return unless changes
125
+
126
+ puts "#{message}:\n\n"
127
+ changes.each do |key, diff|
128
+ puts "#{key}: #{diff[:old]} => #{diff[:new]}"
129
+ end
130
+ puts "\n"
131
+ end
132
+
133
+ # error helpers
134
+
135
+ def check_git_tracking_error
136
+ puts
137
+ puts "Error - please remove '#{fetch(:dotenv_local_path)}' from git:"
138
+ puts
139
+ puts " $ git rm --cached #{fetch(:dotenv_local_path)}"
140
+ puts
141
+ puts 'and gitignore it:'
142
+ puts
143
+ puts " $ echo '#{fetch(:dotenv_local_path)}' >> .gitignore"
144
+ puts
145
+ end
146
+
147
+ def check_config_present_error
148
+ puts
149
+ puts "Error - '#{dotenv_env}' config not present in '#{fetch(:dotenv_local_path)}'."
150
+ puts 'Please populate it.'
151
+ puts
152
+ end
153
+
154
+ def check_dotenv_file_exists_error
155
+ puts
156
+ puts "Error - '#{fetch(:dotenv_local_path)}' file does not exists, and it's required."
157
+ puts
158
+ end
159
+
160
+ # file helpers
161
+ def write_to_file(file, content)
162
+ File.write(file, content)
163
+ end
164
+
165
+ def write_combined_yaml(yamls_combined)
166
+ if yamls_combined.empty?
167
+ info 'No data to write.'
168
+ else
169
+ # write to new file
170
+ info 'writing to config/application.yml'
171
+ write_to_file(dotenv_local_path, yamls_combined.to_yaml)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end