dotenvx 0.0.2 → 4.0.4

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.
data/Cargo.toml ADDED
@@ -0,0 +1,3 @@
1
+ [workspace]
2
+ members = ["ext/dotenvx"]
3
+ resolver = "2"
data/README.md CHANGED
@@ -1,31 +1,83 @@
1
- >
2
- > Warning: work in progress. until complete, please use [github.com/dotenvx/dotenvx](https://github.com/dotenvx/dotenvx)
3
- >
4
-
5
1
  [![dotenvx](https://dotenvx.com/better-banner.png)](https://dotenvx.com)
6
2
 
7
- *a better dotenv*–from the creator of [`dotenv`](https://github.com/motdotla/dotenv).
8
-
9
- * run anywhere (cross-platform)
10
- * multi-environment
11
- * encrypted envs
12
-
13
-  
3
+ *a secure dotenvfrom the creator of [`dotenv`](https://github.com/motdotla/dotenv).*
14
4
 
5
+ `dotenvx` loads plaintext and encrypted dotenv files through the shared
6
+ [`dotenvx-primitives`](https://crates.io/crates/dotenvx-primitives) Rust
7
+ implementation. The native extension is bundled in the gem; users do not need
8
+ Node.js, a dotenvx CLI, or a Rust toolchain.
15
9
 
16
- ### Quickstart [![Gem Version](https://badge.fury.io/rb/dotenvx.svg)](https://badge.fury.io/rb/dotenvx)
17
-
18
- Install and use it in code just like ruby `dotenv`.
10
+ ## Install
19
11
 
20
12
  ```sh
21
13
  gem install dotenvx
22
14
  ```
15
+
16
+ Or add it to a Gemfile:
17
+
18
+ ```ruby
19
+ gem "dotenvx"
20
+ ```
21
+
22
+ ## Use
23
+
24
+ Load `.env` from the current directory:
25
+
23
26
  ```ruby
24
- # index.rb
25
27
  require "dotenvx/load"
28
+ ```
29
+
30
+ Or load explicitly:
31
+
32
+ ```ruby
33
+ require "dotenvx"
34
+
35
+ Dotenvx.load
36
+ Dotenvx.load(".env.local", ".env")
37
+ ```
38
+
39
+ Existing environment variables win by default:
40
+
41
+ ```ruby
42
+ Dotenvx.load(".env")
43
+ ```
44
+
45
+ To replace existing values:
46
+
47
+ ```ruby
48
+ Dotenvx.load(".env", overwrite: true)
49
+ Dotenvx.overwrite(".env")
50
+ ```
51
+
52
+ Parse without changing `ENV`:
26
53
 
27
- puts "Hello #{ENV["HELLO"]}"
54
+ ```ruby
55
+ values = Dotenvx.parse(".env")
28
56
  ```
29
57
 
30
-  
58
+ Raise when a file is missing:
59
+
60
+ ```ruby
61
+ Dotenvx.load!(".env")
62
+ ```
63
+
64
+ Require configuration keys:
65
+
66
+ ```ruby
67
+ Dotenvx.require_keys("DATABASE_URL", "SECRET_KEY")
68
+ ```
69
+
70
+ Encrypted values are decrypted automatically when the matching private key is
71
+ available through the process environment, `<filename>.keys`, or `.env.keys`.
72
+
73
+ ## Native platforms
74
+
75
+ Tagged releases publish variants of the same `dotenvx` gem for:
76
+
77
+ - Linux x86-64
78
+ - Linux ARM64
79
+ - macOS Intel
80
+ - macOS Apple Silicon
81
+ - Windows x64
31
82
 
83
+ RubyGems selects the correct variant during `gem install dotenvx`.
@@ -0,0 +1,17 @@
1
+ [package]
2
+ name = "dotenvx_native"
3
+ version = "4.0.4"
4
+ edition = "2021"
5
+ rust-version = "1.83"
6
+ publish = false
7
+
8
+ [lib]
9
+ name = "dotenvx_native"
10
+ crate-type = ["cdylib"]
11
+
12
+ [dependencies]
13
+ dotenvx-primitives = "=2.2.0"
14
+ magnus = "0.8"
15
+ rb-sys = { version = "0.9.128", default-features = false, features = ["stable-api-compiled-force"] }
16
+ serde = { version = "1", features = ["derive"] }
17
+ serde_json = "1"
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rb_sys/mkmf"
5
+
6
+ create_rust_makefile("dotenvx/dotenvx_native")
@@ -0,0 +1,74 @@
1
+ use dotenvx_primitives::{keyring, parse, KeyringOptions, ParseOptions, ParseResult, Value};
2
+ use magnus::{function, prelude::*, Error, Ruby};
3
+ use serde::Deserialize;
4
+ use std::collections::HashMap;
5
+ use std::path::PathBuf;
6
+
7
+ type StringPairs = Vec<(String, String)>;
8
+
9
+ fn runtime_error(message: impl Into<String>) -> Error {
10
+ let ruby = Ruby::get().expect("Ruby VM is not available");
11
+ Error::new(ruby.exception_runtime_error(), message.into())
12
+ }
13
+
14
+ fn scalar_values(values: HashMap<String, Value>) -> StringPairs {
15
+ values
16
+ .into_iter()
17
+ .filter_map(|(key, value)| match value {
18
+ Value::Scalar(value) => Some((key, value)),
19
+ Value::Array(_) => None,
20
+ })
21
+ .collect()
22
+ }
23
+
24
+ fn parse_result(result: ParseResult) -> Result<(StringPairs, StringPairs), Error> {
25
+ if !result.errors.is_empty() {
26
+ let message = result
27
+ .errors
28
+ .iter()
29
+ .map(ToString::to_string)
30
+ .collect::<Vec<_>>()
31
+ .join("\n");
32
+ return Err(runtime_error(message));
33
+ }
34
+
35
+ Ok((scalar_values(result.parsed), scalar_values(result.injected)))
36
+ }
37
+
38
+ #[derive(Deserialize)]
39
+ struct ParseInput {
40
+ source: String,
41
+ process_env: HashMap<String, String>,
42
+ overwrite: bool,
43
+ key_files: Vec<String>,
44
+ }
45
+
46
+ fn parse_dotenv(input_json: String) -> Result<(StringPairs, StringPairs), Error> {
47
+ let input = serde_json::from_str::<ParseInput>(&input_json)
48
+ .map_err(|error| runtime_error(error.to_string()))?;
49
+ let process_env = input.process_env;
50
+ let ring = keyring(&KeyringOptions {
51
+ process_env: process_env.clone(),
52
+ key_files: input.key_files.into_iter().map(PathBuf::from).collect(),
53
+ ..Default::default()
54
+ })
55
+ .map_err(|error| runtime_error(error.to_string()))?;
56
+
57
+ parse_result(parse(
58
+ &input.source,
59
+ &ParseOptions {
60
+ process_env,
61
+ overload: input.overwrite,
62
+ ring,
63
+ ..Default::default()
64
+ },
65
+ ))
66
+ }
67
+
68
+ #[magnus::init]
69
+ fn init(ruby: &Ruby) -> Result<(), Error> {
70
+ let dotenvx = ruby.define_module("Dotenvx")?;
71
+ let native = dotenvx.define_module("Native")?;
72
+ native.define_singleton_method("parse_dotenv", function!(parse_dotenv, 1))?;
73
+ Ok(())
74
+ }
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dotenvx"
4
+
5
+ Dotenvx.load
data/lib/dotenvx/rails.rb CHANGED
@@ -1,4 +1,7 @@
1
1
  require "dotenvx"
2
+ require "active_support/notifications"
3
+ require "rails/railtie"
4
+ require "pathname"
2
5
 
3
6
  # Fix for rake tasks loading in development
4
7
  #
@@ -30,9 +33,33 @@ rescue LoadError, ArgumentError
30
33
  end
31
34
 
32
35
  module Dotenvx
33
- class Railtie
36
+ class Railtie < Rails::Railtie
37
+ config.before_configuration { Dotenvx::Railtie.instance.load }
38
+
34
39
  def load
40
+ return if @loaded
41
+
35
42
  Dotenvx.load(*dotenvx_files)
43
+ @loaded = true
44
+ end
45
+
46
+ private
47
+
48
+ def dotenvx_files
49
+ environment = Rails.env
50
+ files = [
51
+ root.join(".env.#{environment}.local")
52
+ ]
53
+ files << root.join(".env.local") unless environment == "test"
54
+ files.concat([
55
+ root.join(".env.#{environment}"),
56
+ root.join(".env")
57
+ ])
58
+ files
59
+ end
60
+
61
+ def root
62
+ Pathname.new(Rails.root || ENV["RAILS_ROOT"] || Dir.pwd)
36
63
  end
37
64
  end
38
65
  end
@@ -1,3 +1,3 @@
1
1
  module Dotenvx
2
- VERSION = "0.0.2"
2
+ VERSION = "4.0.4"
3
3
  end
data/lib/dotenvx.rb CHANGED
@@ -1,7 +1,141 @@
1
1
  require "dotenvx/version"
2
+ require "dotenvx/dotenvx_native"
3
+ require "json"
4
+ require "pathname"
2
5
 
3
6
  module Dotenvx
4
- def load(*filenames)
5
- raise "go to [github.com/dotenvx/dotenvx] and follow ruby directions there"
7
+ class MissingKeys < RuntimeError
8
+ attr_reader :keys
9
+
10
+ def initialize(keys)
11
+ @keys = keys
12
+ super("Missing required configuration keys: #{keys.join(", ")}")
13
+ end
14
+ end
15
+
16
+ class << self
17
+ attr_accessor :instrumenter
18
+
19
+ def load(*filenames, overwrite: false, ignore: true)
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
28
+ end
29
+
30
+ def load!(*filenames)
31
+ load(*filenames, ignore: false)
32
+ end
33
+
34
+ def overwrite(*filenames)
35
+ load(*filenames, overwrite: true)
36
+ end
37
+ alias overload overwrite
38
+
39
+ def overwrite!(*filenames)
40
+ load(*filenames, overwrite: true, ignore: false)
41
+ end
42
+ alias overload! overwrite!
43
+
44
+ def parse(*filenames, overwrite: false, ignore: true)
45
+ parse_files(*filenames, overwrite: overwrite, ignore: ignore).first
46
+ end
47
+
48
+ def update(env = {}, overwrite: false)
49
+ changed = {}
50
+ env.each do |key, value|
51
+ key = key.to_s
52
+ next if ENV.key?(key) && overwrite == false
53
+
54
+ if ENV.key?(key) && overwrite == :warn
55
+ warn "Warning: dotenvx not overwriting ENV[#{key.inspect}]"
56
+ next
57
+ end
58
+ unless [true, false, :warn].include?(overwrite)
59
+ raise ArgumentError, "Invalid value for overwrite: #{overwrite.inspect}"
60
+ end
61
+
62
+ ENV[key] = value.to_s
63
+ changed[key] = value.to_s
64
+ end
65
+ changed
66
+ end
67
+
68
+ def require_keys(*keys)
69
+ missing = keys.flatten.map(&:to_s) - ENV.keys
70
+ raise MissingKeys, missing unless missing.empty?
71
+ end
72
+
73
+ private
74
+
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
+
125
+ def key_files(path)
126
+ candidates = ["#{path}.keys", File.join(File.dirname(path), ".env.keys")]
127
+ candidates.select { |candidate| File.file?(candidate) }
128
+ .uniq
129
+ end
130
+ end
131
+
132
+ class Environment < Hash
133
+ attr_reader :filename
134
+
135
+ def initialize(filename, values)
136
+ @filename = filename
137
+ super()
138
+ update(values)
139
+ end
6
140
  end
7
141
  end
metadata CHANGED
@@ -1,29 +1,56 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotenvx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 4.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - motdotla
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-10-18 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rb_sys
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.9'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.9'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: rake
15
28
  requirement: !ruby/object:Gem::Requirement
16
29
  requirements:
17
- - - ">="
30
+ - - "~>"
18
31
  - !ruby/object:Gem::Version
19
- version: '0'
32
+ version: '13.0'
20
33
  type: :development
21
34
  prerelease: false
22
35
  version_requirements: !ruby/object:Gem::Requirement
23
36
  requirements:
24
- - - ">="
37
+ - - "~>"
25
38
  - !ruby/object:Gem::Version
26
- version: '0'
39
+ version: '13.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake-compiler
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.3'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.3'
27
54
  - !ruby/object:Gem::Dependency
28
55
  name: rspec
29
56
  requirement: !ruby/object:Gem::Requirement
@@ -56,22 +83,20 @@ description: "[dotenvx.com] a better dotenv–from the creator of `dotenv`"
56
83
  email:
57
84
  - mot@mot.la
58
85
  executables: []
59
- extensions: []
86
+ extensions:
87
+ - ext/dotenvx/extconf.rb
60
88
  extra_rdoc_files: []
61
89
  files:
62
- - ".gitignore"
63
- - ".rspec"
64
90
  - CHANGELOG.md
65
- - DEVELOPMENT.md
66
- - Gemfile
67
- - Gemfile.lock
91
+ - Cargo.lock
92
+ - Cargo.toml
68
93
  - LICENSE
69
94
  - README.md
70
- - Rakefile
71
- - dotenvx-rails.gemspec
72
- - dotenvx.gemspec
73
- - lib/dotenvx-rails.rb
95
+ - ext/dotenvx/Cargo.toml
96
+ - ext/dotenvx/extconf.rb
97
+ - ext/dotenvx/src/lib.rs
74
98
  - lib/dotenvx.rb
99
+ - lib/dotenvx/load.rb
75
100
  - lib/dotenvx/rails.rb
76
101
  - lib/dotenvx/version.rb
77
102
  homepage: https://github.com/dotenvx/dotenvx-ruby
@@ -81,7 +106,6 @@ metadata:
81
106
  homepage_uri: https://github.com/dotenvx/dotenvx-ruby
82
107
  source_code_uri: https://github.com/dotenvx/dotenvx-ruby
83
108
  changelog_uri: https://github.com/dotenvx/dotenvx-ruby
84
- post_install_message:
85
109
  rdoc_options: []
86
110
  require_paths:
87
111
  - lib
@@ -89,15 +113,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
89
113
  requirements:
90
114
  - - ">="
91
115
  - !ruby/object:Gem::Version
92
- version: 2.3.0
116
+ version: 2.6.0
93
117
  required_rubygems_version: !ruby/object:Gem::Requirement
94
118
  requirements:
95
119
  - - ">="
96
120
  - !ruby/object:Gem::Version
97
- version: '0'
121
+ version: 3.3.11
98
122
  requirements: []
99
- rubygems_version: 3.5.11
100
- signing_key:
123
+ rubygems_version: 3.6.9
101
124
  specification_version: 4
102
125
  summary: "[dotenvx.com] a better dotenv–from the creator of `dotenv`"
103
126
  test_files: []
data/.gitignore DELETED
@@ -1,13 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
- .DS_Store
13
- .byebug_history
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper
data/DEVELOPMENT.md DELETED
@@ -1,4 +0,0 @@
1
- ## Development
2
-
3
- 1. Bump `dotenvx/version.rb` and tag version
4
- 2. rake release
data/Gemfile DELETED
@@ -1,8 +0,0 @@
1
- source "https://rubygems.org"
2
-
3
- # Specify your gem's dependencies in dotenvx.gemspec
4
- gemspec name: "dotenvx"
5
- gemspec name: "dotenvx-rails"
6
-
7
- gem "rake", "~> 12.0"
8
- gem "rspec", "~> 3.0"
data/Gemfile.lock DELETED
@@ -1,42 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- dotenvx (0.0.1)
5
- dotenvx-rails (0.0.1)
6
- dotenvx (= 0.0.1)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- byebug (11.1.3)
12
- diff-lcs (1.5.1)
13
- rake (12.3.3)
14
- rspec (3.13.0)
15
- rspec-core (~> 3.13.0)
16
- rspec-expectations (~> 3.13.0)
17
- rspec-mocks (~> 3.13.0)
18
- rspec-core (3.13.1)
19
- rspec-support (~> 3.13.0)
20
- rspec-expectations (3.13.3)
21
- diff-lcs (>= 1.2.0, < 2.0)
22
- rspec-support (~> 3.13.0)
23
- rspec-mocks (3.13.2)
24
- diff-lcs (>= 1.2.0, < 2.0)
25
- rspec-support (~> 3.13.0)
26
- rspec-support (3.13.1)
27
- spring (4.2.1)
28
-
29
- PLATFORMS
30
- arm64-darwin-23
31
- ruby
32
-
33
- DEPENDENCIES
34
- byebug
35
- dotenvx!
36
- dotenvx-rails!
37
- rake (~> 12.0)
38
- rspec (~> 3.0)
39
- spring
40
-
41
- BUNDLED WITH
42
- 2.5.11
data/Rakefile DELETED
@@ -1,35 +0,0 @@
1
- #!/usr/bin/env rake
2
-
3
- require "bundler/gem_helper"
4
-
5
- namespace "dotenvx" do
6
- Bundler::GemHelper.install_tasks name: "dotenvx"
7
- end
8
-
9
- class DotenvxRailsGemHelper < Bundler::GemHelper
10
- def guard_already_tagged
11
- # noop
12
- end
13
-
14
- def tag_version
15
- # noop
16
- end
17
- end
18
-
19
- namespace "dotenvx-rails" do
20
- DotenvxRailsGemHelper.install_tasks name: "dotenvx-rails"
21
- end
22
-
23
- task build: ["dotenvx:build", "dotenvx-rails:build"]
24
- task install: ["dotenvx:install", "dotenvx-rails:install"]
25
- task release: ["dotenvx:release", "dotenvx-rails:release"]
26
-
27
- require "rspec/core/rake_task"
28
-
29
- desc "Run all specs"
30
- RSpec::Core::RakeTask.new(:spec) do |t|
31
- t.rspec_opts = %w[--color]
32
- t.verbose = false
33
- end
34
-
35
- task :default => :spec
@@ -1,32 +0,0 @@
1
- require_relative 'lib/dotenvx/version'
2
-
3
- Gem::Specification.new "dotenvx-rails" do |spec|
4
- spec.name = "dotenvx-rails"
5
- spec.version = Dotenvx::VERSION
6
- spec.authors = ["motdotla"]
7
- spec.email = ["mot@mot.la"]
8
-
9
- spec.summary = %q{[dotenvx.com] a better dotenv–from the creator of `dotenv`}
10
- spec.description = %q{[dotenvx.com] a better dotenv–from the creator of `dotenv`}
11
- spec.homepage = "https://github.com/dotenvx/dotenvx-ruby"
12
- spec.license = "BSD-3-Clause"
13
- spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
-
15
- spec.metadata["homepage_uri"] = spec.homepage
16
- spec.metadata["source_code_uri"] = "https://github.com/dotenvx/dotenvx-ruby"
17
- spec.metadata["changelog_uri"] = "https://github.com/dotenvx/dotenvx-ruby"
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
- end
24
- spec.bindir = "exe"
25
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
- spec.require_paths = ["lib"]
27
-
28
- spec.add_dependency "dotenvx", Dotenvx::VERSION
29
-
30
- spec.add_development_dependency "spring"
31
- spec.add_development_dependency "byebug"
32
- end