Skip to content

Commit da3172d

Browse files
committed
add injected message
1 parent a1e6645 commit da3172d

8 files changed

Lines changed: 98 additions & 39 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5-
## [Unreleased](https://github.com/dotenvx/dotenvx-ruby/compare/v4.0.0...main)
5+
## [Unreleased](https://github.com/dotenvx/dotenvx-ruby/compare/v4.0.4...main)
6+
7+
## [4.0.4](https://github.com/dotenvx/dotenvx-ruby/compare/v4.0.3...v4.0.4)
8+
9+
- Report the unique variables injected from readable dotenv files.
610

711
## [4.0.0](https://github.com/dotenvx/dotenvx-ruby/compare/v0.0.2...v4.0.0)
812

‎Cargo.lock‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Gemfile.lock‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
PATH
22
remote: .
33
specs:
4-
dotenvx (4.0.3)
4+
dotenvx (4.0.4)
55
rb_sys (~> 0.9)
6-
dotenvx-rails (4.0.3)
7-
dotenvx (= 4.0.3)
6+
dotenvx-rails (4.0.4)
7+
dotenvx (= 4.0.4)
88
railties (>= 5.0)
99

1010
GEM

‎ext/dotenvx/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "dotenvx_native"
3-
version = "4.0.3"
3+
version = "4.0.4"
44
edition = "2021"
55
rust-version = "1.83"
66
publish = false

‎lib/dotenvx.rb‎

Lines changed: 60 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require "dotenvx/version"
22
require "dotenvx/dotenvx_native"
33
require "json"
4+
require "pathname"
45

56
module Dotenvx
67
class MissingKeys < RuntimeError
@@ -16,8 +17,14 @@ class << self
1617
attr_accessor :instrumenter
1718

1819
def load(*filenames, overwrite: false, ignore: true)
19-
env = parse(*filenames, overwrite: overwrite, ignore: ignore)
20-
update(env, overwrite: overwrite)
20+
env, injected_keys, loaded_paths = parse_files(
21+
*filenames,
22+
overwrite: overwrite,
23+
ignore: ignore
24+
)
25+
changed = update(env, overwrite: overwrite)
26+
log_injected(injected_keys, loaded_paths)
27+
changed
2128
end
2229

2330
def load!(*filenames)
@@ -35,31 +42,7 @@ def overwrite!(*filenames)
3542
alias overload! overwrite!
3643

3744
def parse(*filenames, overwrite: false, ignore: true)
38-
filenames = [".env"] if filenames.empty?
39-
filenames = filenames.flatten.reverse if overwrite
40-
41-
process_env = ENV.to_h
42-
filenames.reduce({}) do |values, filename|
43-
path = File.expand_path(filename)
44-
begin
45-
source = File.binread(path).sub(/\A\xEF\xBB\xBF/, "").force_encoding(Encoding::UTF_8)
46-
rescue Errno::ENOENT, Errno::EISDIR
47-
raise unless ignore
48-
next values
49-
end
50-
51-
parsed, = Native.parse_dotenv(JSON.generate(
52-
source: source,
53-
process_env: process_env,
54-
overwrite: overwrite == true,
55-
key_files: key_files(path)
56-
))
57-
parsed = parsed.to_h
58-
process_env.merge!(parsed)
59-
values.merge!(parsed)
60-
yield Environment.new(path, parsed) if block_given?
61-
values
62-
end
45+
parse_files(*filenames, overwrite: overwrite, ignore: ignore).first
6346
end
6447

6548
def update(env = {}, overwrite: false)
@@ -89,6 +72,56 @@ def require_keys(*keys)
8972

9073
private
9174

75+
def parse_files(*filenames, overwrite: false, ignore: true)
76+
filenames = [".env"] if filenames.empty?
77+
filenames = filenames.flatten.reverse if overwrite
78+
79+
process_env = ENV.to_h
80+
injected_keys = {}
81+
loaded_paths = []
82+
values = filenames.reduce({}) do |accumulator, filename|
83+
path = File.expand_path(filename)
84+
begin
85+
source = File.binread(path).sub(/\A\xEF\xBB\xBF/, "").force_encoding(Encoding::UTF_8)
86+
rescue Errno::ENOENT, Errno::EISDIR
87+
raise unless ignore
88+
next accumulator
89+
end
90+
91+
parsed, injected = Native.parse_dotenv(JSON.generate(
92+
source: source,
93+
process_env: process_env,
94+
overwrite: overwrite == true,
95+
key_files: key_files(path)
96+
))
97+
parsed = parsed.to_h
98+
injected.each { |key, _value| injected_keys[key] = true }
99+
loaded_paths << path
100+
process_env.merge!(parsed)
101+
accumulator.merge!(parsed)
102+
yield Environment.new(path, parsed) if block_given?
103+
accumulator
104+
end
105+
[values, injected_keys.keys, loaded_paths]
106+
end
107+
108+
def log_injected(injected_keys, loaded_paths)
109+
message = "⟐ injected env (#{injected_keys.length})"
110+
unless loaded_paths.empty?
111+
paths = loaded_paths.map { |path| readable_path(path) }
112+
message = "#{message} from #{paths.join(", ")}"
113+
end
114+
warn message
115+
end
116+
117+
def readable_path(path)
118+
pathname = Pathname.new(path)
119+
relative = pathname.relative_path_from(Pathname.pwd).to_s
120+
relative.start_with?("../") ? pathname.to_s : relative
121+
rescue ArgumentError
122+
pathname.to_s
123+
end
124+
92125
def key_files(path)
93126
candidates = ["#{path}.keys", File.join(File.dirname(path), ".env.keys")]
94127
candidates.select { |candidate| File.file?(candidate) }

‎lib/dotenvx/rails.rb‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ class Railtie < Rails::Railtie
3737
config.before_configuration { Dotenvx::Railtie.instance.load }
3838

3939
def load
40+
return if @loaded
41+
4042
Dotenvx.load(*dotenvx_files)
43+
@loaded = true
4144
end
4245

4346
private

‎lib/dotenvx/version.rb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Dotenvx
2-
VERSION = "4.0.3"
2+
VERSION = "4.0.4"
33
end

‎spec/dotenvx_spec.rb‎

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
with_env_file("HELLO=World\nEXISTING=file\n") do |path|
1212
ENV["EXISTING"] = "environment"
1313

14-
expect(described_class.load(path)).to eq("HELLO" => "World")
14+
expect { described_class.load(path) }
15+
.to output("⟐ injected env (1) from #{path}\n").to_stderr
1516
expect(ENV["HELLO"]).to eq("World")
1617
expect(ENV["EXISTING"]).to eq("environment")
1718
end
@@ -21,25 +22,41 @@
2122
with_env_file("EXISTING=file\n") do |path|
2223
ENV["EXISTING"] = "environment"
2324

24-
expect(described_class.overwrite(path)).to eq("EXISTING" => "file")
25+
expect { described_class.overwrite(path) }
26+
.to output("⟐ injected env (1) from #{path}\n").to_stderr
2527
expect(ENV["EXISTING"]).to eq("file")
2628
end
2729
end
2830

2931
it "parses without changing ENV" do
3032
with_env_file("PARSED=yes\n") do |path|
33+
expect { described_class.parse(path) }.not_to output.to_stderr
3134
expect(described_class.parse(path)).to eq("PARSED" => "yes")
3235
expect(ENV).not_to have_key("PARSED")
3336
end
3437
end
3538

39+
it "counts unique injected keys across readable files" do
40+
Dir.mktmpdir do |directory|
41+
first = File.join(directory, ".env.local")
42+
second = File.join(directory, ".env")
43+
File.write(first, "FIRST=one\nSHARED=local\n")
44+
File.write(second, "SECOND=two\nSHARED=base\nEXISTING=file\n")
45+
ENV["EXISTING"] = "environment"
46+
47+
expect { described_class.load(first, second) }
48+
.to output("⟐ injected env (3) from #{first}, #{second}\n").to_stderr
49+
end
50+
end
51+
3652
it "decrypts with the adjacent dotenvx key file" do
3753
Dir.mktmpdir do |directory|
3854
path = File.join(directory, ".env")
3955
File.write(path, "HELLO=#{encrypted_world}\n")
4056
File.write("#{path}.keys", "DOTENV_PRIVATE_KEY=#{private_key}\n")
4157

42-
expect(described_class.load(path)).to eq("HELLO" => "World")
58+
expect { described_class.load(path) }
59+
.to output("⟐ injected env (1) from #{path}\n").to_stderr
4360
expect(ENV["HELLO"]).to eq("World")
4461
end
4562
end
@@ -50,15 +67,17 @@
5067
File.write(path, "HELLO=#{encrypted_world}\n")
5168
File.write(File.join(directory, ".env.keys"), "DOTENV_PRIVATE_KEY=#{private_key}\n")
5269

53-
expect(described_class.load(path)).to eq("HELLO" => "World")
70+
expect { described_class.load(path) }
71+
.to output("⟐ injected env (1) from #{path}\n").to_stderr
5472
expect(ENV["HELLO"]).to eq("World")
5573
end
5674
end
5775

5876
it "ignores missing files by default and raises through load!" do
5977
missing = File.join(Dir.tmpdir, "dotenvx-does-not-exist")
6078

61-
expect(described_class.load(missing)).to eq({})
79+
expect { expect(described_class.load(missing)).to eq({}) }
80+
.to output("⟐ injected env (0)\n").to_stderr
6281
expect { described_class.load!(missing) }.to raise_error(Errno::ENOENT)
6382
end
6483

0 commit comments

Comments
 (0)