philiprehberger-env_diff 0.1.7 → 0.2.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +49 -1
- data/lib/philiprehberger/env_diff/diff.rb +83 -10
- data/lib/philiprehberger/env_diff/version.rb +1 -1
- data/lib/philiprehberger/env_diff.rb +9 -0
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 256215c35e39e22093122c4c9d2452e3298beb699cbb84189a14dbf88ce75dbb
|
|
4
|
+
data.tar.gz: ddada81c036244804246bd9e8c5c790778a9621bf412aef912cedf15ecd4e116
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ef02926fa7b051d80cde13876739650db6c3279a8f068836292ff2676633668cdabc6256e6c50ca82a37f4a806ed942deebdbecd703f210dcfcce14f0171db4e
|
|
7
|
+
data.tar.gz: 9d991cd745f294cb1d989144ba6136d6f64a52af32d7107d05d38193bca92a6740add34ad57e43dae9fa4103f1256f1d6852fbb87e56ca44d9cb371092b1a081
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-04-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `Diff#summary(mask:)` — mask values for sensitive keys using string or regex patterns
|
|
14
|
+
- `Diff#to_h` — structured hash output with added, removed, changed, and unchanged categories
|
|
15
|
+
- `Diff#to_json` — JSON serialization of the structured hash
|
|
16
|
+
- `Diff#filter(pattern:)` — return a new Diff containing only keys matching a regex pattern
|
|
17
|
+
- `Diff#stats` — returns counts of added, removed, changed, unchanged, and total keys
|
|
18
|
+
- `EnvDiff.from_system(target)` — compare current ENV against a target hash or .env file path
|
|
19
|
+
|
|
20
|
+
## [0.1.8] - 2026-03-31
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
- Add GitHub issue templates, dependabot config, and PR template
|
|
24
|
+
|
|
10
25
|
## [0.1.7] - 2026-03-31
|
|
11
26
|
|
|
12
27
|
### Changed
|
data/README.md
CHANGED
|
@@ -46,6 +46,49 @@ puts diff.summary
|
|
|
46
46
|
# ~ DATABASE_URL: "postgres://localhost/dev" -> "postgres://prod-host/app"
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
+
### Masked summary
|
|
50
|
+
|
|
51
|
+
Hide sensitive values in the summary output using exact strings or regex patterns:
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
puts diff.summary(mask: ["SECRET", /PASSWORD|TOKEN/])
|
|
55
|
+
# + NEW_KEY
|
|
56
|
+
# - OLD_KEY
|
|
57
|
+
# ~ SECRET: *** -> ***
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Structured output
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
diff.to_h
|
|
64
|
+
# => { added: { "NEW_KEY" => "added" }, removed: { "OLD_KEY" => "remove_me" },
|
|
65
|
+
# changed: { "DATABASE_URL" => { source: "postgres://localhost/dev", target: "postgres://prod-host/app" } },
|
|
66
|
+
# unchanged: { "SECRET" => "abc" } }
|
|
67
|
+
|
|
68
|
+
diff.to_json # => JSON string of the structured hash
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Filter by pattern
|
|
72
|
+
|
|
73
|
+
```ruby
|
|
74
|
+
db_diff = diff.filter(pattern: /^DATABASE/)
|
|
75
|
+
db_diff.changed.keys # => ["DATABASE_URL"]
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Stats
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
diff.stats
|
|
82
|
+
# => { added: 1, removed: 1, changed: 1, unchanged: 1, total: 4 }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Compare against system ENV
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
diff = Philiprehberger::EnvDiff.from_system({ "APP_ENV" => "production" })
|
|
89
|
+
diff = Philiprehberger::EnvDiff.from_system(".env.production")
|
|
90
|
+
```
|
|
91
|
+
|
|
49
92
|
### Compare .env files
|
|
50
93
|
|
|
51
94
|
```ruby
|
|
@@ -72,12 +115,17 @@ ENV
|
|
|
72
115
|
| `EnvDiff.compare(source, target)` | Compare two hashes and return a `Diff` |
|
|
73
116
|
| `EnvDiff.from_hash(hash_a, hash_b)` | Alias for `compare` |
|
|
74
117
|
| `EnvDiff.from_env_file(path_a, path_b)` | Parse two `.env` files and compare them |
|
|
118
|
+
| `EnvDiff.from_system(target)` | Compare current `ENV` against a target hash or `.env` file path |
|
|
75
119
|
| `Diff#added` | Array of keys in target but not source |
|
|
76
120
|
| `Diff#removed` | Array of keys in source but not target |
|
|
77
121
|
| `Diff#changed` | Hash of keys with different values |
|
|
78
122
|
| `Diff#unchanged` | Array of keys with identical values |
|
|
79
123
|
| `Diff#changed?` | `true` if any differences exist |
|
|
80
|
-
| `Diff#summary` | Human-readable multiline diff string |
|
|
124
|
+
| `Diff#summary(mask: [])` | Human-readable multiline diff string with optional value masking |
|
|
125
|
+
| `Diff#to_h` | Structured hash with `:added`, `:removed`, `:changed`, `:unchanged` |
|
|
126
|
+
| `Diff#to_json` | JSON serialization of the structured hash |
|
|
127
|
+
| `Diff#filter(pattern:)` | New `Diff` containing only keys matching the regex pattern |
|
|
128
|
+
| `Diff#stats` | Hash of counts: `:added`, `:removed`, `:changed`, `:unchanged`, `:total` |
|
|
81
129
|
| `Parser.parse(content)` | Parse `.env` string into a hash |
|
|
82
130
|
| `Parser.parse_file(path:)` | Read and parse a `.env` file |
|
|
83
131
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
3
5
|
module Philiprehberger
|
|
4
6
|
module EnvDiff
|
|
5
7
|
# Represents the result of comparing two sets of environment variables.
|
|
@@ -21,6 +23,8 @@ module Philiprehberger
|
|
|
21
23
|
# @param source [Hash] the baseline environment hash
|
|
22
24
|
# @param target [Hash] the environment hash to compare against
|
|
23
25
|
def initialize(source, target)
|
|
26
|
+
@source = source
|
|
27
|
+
@target = target
|
|
24
28
|
@added = (target.keys - source.keys).sort
|
|
25
29
|
@removed = (source.keys - target.keys).sort
|
|
26
30
|
@changed = build_changed(source, target)
|
|
@@ -36,15 +40,58 @@ module Philiprehberger
|
|
|
36
40
|
|
|
37
41
|
# Human-readable multiline summary of all differences.
|
|
38
42
|
#
|
|
43
|
+
# @param mask [Array<String, Regexp>] patterns for keys whose values should be masked
|
|
39
44
|
# @return [String] formatted summary
|
|
40
|
-
def summary
|
|
45
|
+
def summary(mask: [])
|
|
41
46
|
lines = []
|
|
42
|
-
append_added(lines)
|
|
43
|
-
append_removed(lines)
|
|
44
|
-
append_changed(lines)
|
|
47
|
+
append_added(lines, mask)
|
|
48
|
+
append_removed(lines, mask)
|
|
49
|
+
append_changed(lines, mask)
|
|
45
50
|
lines.empty? ? 'No differences found.' : lines.join("\n")
|
|
46
51
|
end
|
|
47
52
|
|
|
53
|
+
# Structured hash representation of the diff.
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash] with :added, :removed, :changed, :unchanged keys
|
|
56
|
+
def to_h
|
|
57
|
+
{
|
|
58
|
+
added: @added.to_h { |k| [k, @target[k]] },
|
|
59
|
+
removed: @removed.to_h { |k| [k, @source[k]] },
|
|
60
|
+
changed: @changed.transform_values { |v| { source: v[:source], target: v[:target] } },
|
|
61
|
+
unchanged: @unchanged.to_h { |k| [k, @source[k]] }
|
|
62
|
+
}
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# JSON serialization of the structured hash.
|
|
66
|
+
#
|
|
67
|
+
# @return [String] JSON string
|
|
68
|
+
def to_json(*_args)
|
|
69
|
+
JSON.generate(to_h)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Return a new Diff containing only keys matching the given pattern.
|
|
73
|
+
#
|
|
74
|
+
# @param pattern [Regexp] regex pattern to match keys against
|
|
75
|
+
# @return [Diff] filtered diff
|
|
76
|
+
def filter(pattern:)
|
|
77
|
+
filtered_source = @source.select { |k, _| k.match?(pattern) }
|
|
78
|
+
filtered_target = @target.select { |k, _| k.match?(pattern) }
|
|
79
|
+
Diff.new(filtered_source, filtered_target)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Statistics about the diff.
|
|
83
|
+
#
|
|
84
|
+
# @return [Hash] counts of added, removed, changed, unchanged, and total keys
|
|
85
|
+
def stats
|
|
86
|
+
{
|
|
87
|
+
added: @added.length,
|
|
88
|
+
removed: @removed.length,
|
|
89
|
+
changed: @changed.length,
|
|
90
|
+
unchanged: @unchanged.length,
|
|
91
|
+
total: @added.length + @removed.length + @changed.length + @unchanged.length
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
48
95
|
private
|
|
49
96
|
|
|
50
97
|
def build_changed(source, target)
|
|
@@ -61,17 +108,43 @@ module Philiprehberger
|
|
|
61
108
|
common.select { |key| source[key] == target[key] }.sort
|
|
62
109
|
end
|
|
63
110
|
|
|
64
|
-
def
|
|
65
|
-
|
|
111
|
+
def masked?(key, mask)
|
|
112
|
+
mask.any? do |pattern|
|
|
113
|
+
pattern.is_a?(Regexp) ? key.match?(pattern) : key == pattern
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def mask_value(_value, key, mask)
|
|
118
|
+
masked?(key, mask) ? '***' : yield
|
|
66
119
|
end
|
|
67
120
|
|
|
68
|
-
def
|
|
69
|
-
@
|
|
121
|
+
def append_added(lines, mask)
|
|
122
|
+
@added.each do |key|
|
|
123
|
+
lines << if masked?(key, mask)
|
|
124
|
+
"+ #{key}=***"
|
|
125
|
+
else
|
|
126
|
+
"+ #{key}"
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def append_removed(lines, mask)
|
|
132
|
+
@removed.each do |key|
|
|
133
|
+
lines << if masked?(key, mask)
|
|
134
|
+
"- #{key}=***"
|
|
135
|
+
else
|
|
136
|
+
"- #{key}"
|
|
137
|
+
end
|
|
138
|
+
end
|
|
70
139
|
end
|
|
71
140
|
|
|
72
|
-
def append_changed(lines)
|
|
141
|
+
def append_changed(lines, mask)
|
|
73
142
|
@changed.each do |key, vals|
|
|
74
|
-
lines <<
|
|
143
|
+
lines << if masked?(key, mask)
|
|
144
|
+
"~ #{key}: *** -> ***"
|
|
145
|
+
else
|
|
146
|
+
"~ #{key}: #{vals[:source].inspect} -> #{vals[:target].inspect}"
|
|
147
|
+
end
|
|
75
148
|
end
|
|
76
149
|
end
|
|
77
150
|
end
|
|
@@ -34,5 +34,14 @@ module Philiprehberger
|
|
|
34
34
|
def self.from_env_file(path_a, path_b)
|
|
35
35
|
compare(Parser.parse_file(path: path_a), Parser.parse_file(path: path_b))
|
|
36
36
|
end
|
|
37
|
+
|
|
38
|
+
# Compare the current system ENV against a target hash or .env file path.
|
|
39
|
+
#
|
|
40
|
+
# @param target [Hash, String] a hash of target variables or a path to a .env file
|
|
41
|
+
# @return [Diff] the computed differences between ENV and target
|
|
42
|
+
def self.from_system(target)
|
|
43
|
+
target_hash = target.is_a?(String) ? Parser.parse_file(path: target) : target
|
|
44
|
+
compare(ENV.to_h, target_hash)
|
|
45
|
+
end
|
|
37
46
|
end
|
|
38
47
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-env_diff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Parse .env files or environment hashes, compare them, and get a clear
|
|
14
14
|
report of added, removed, changed, and unchanged variables.
|
|
@@ -25,11 +25,11 @@ files:
|
|
|
25
25
|
- lib/philiprehberger/env_diff/diff.rb
|
|
26
26
|
- lib/philiprehberger/env_diff/parser.rb
|
|
27
27
|
- lib/philiprehberger/env_diff/version.rb
|
|
28
|
-
homepage: https://
|
|
28
|
+
homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-env_diff
|
|
29
29
|
licenses:
|
|
30
30
|
- MIT
|
|
31
31
|
metadata:
|
|
32
|
-
homepage_uri: https://
|
|
32
|
+
homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-env_diff
|
|
33
33
|
source_code_uri: https://github.com/philiprehberger/rb-env-diff
|
|
34
34
|
changelog_uri: https://github.com/philiprehberger/rb-env-diff/blob/main/CHANGELOG.md
|
|
35
35
|
bug_tracker_uri: https://github.com/philiprehberger/rb-env-diff/issues
|