dotenvx 0.0.2 → 4.0.3

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.3"
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,30 @@ 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
35
40
  Dotenvx.load(*dotenvx_files)
36
41
  end
42
+
43
+ private
44
+
45
+ def dotenvx_files
46
+ environment = Rails.env
47
+ files = [
48
+ root.join(".env.#{environment}.local")
49
+ ]
50
+ files << root.join(".env.local") unless environment == "test"
51
+ files.concat([
52
+ root.join(".env.#{environment}"),
53
+ root.join(".env")
54
+ ])
55
+ files
56
+ end
57
+
58
+ def root
59
+ Pathname.new(Rails.root || ENV["RAILS_ROOT"] || Dir.pwd)
60
+ end
37
61
  end
38
62
  end
@@ -1,3 +1,3 @@
1
1
  module Dotenvx
2
- VERSION = "0.0.2"
2
+ VERSION = "4.0.3"
3
3
  end
data/lib/dotenvx.rb CHANGED
@@ -1,7 +1,108 @@
1
1
  require "dotenvx/version"
2
+ require "dotenvx/dotenvx_native"
3
+ require "json"
2
4
 
3
5
  module Dotenvx
4
- def load(*filenames)
5
- raise "go to [github.com/dotenvx/dotenvx] and follow ruby directions there"
6
+ class MissingKeys < RuntimeError
7
+ attr_reader :keys
8
+
9
+ def initialize(keys)
10
+ @keys = keys
11
+ super("Missing required configuration keys: #{keys.join(", ")}")
12
+ end
13
+ end
14
+
15
+ class << self
16
+ attr_accessor :instrumenter
17
+
18
+ def load(*filenames, overwrite: false, ignore: true)
19
+ env = parse(*filenames, overwrite: overwrite, ignore: ignore)
20
+ update(env, overwrite: overwrite)
21
+ end
22
+
23
+ def load!(*filenames)
24
+ load(*filenames, ignore: false)
25
+ end
26
+
27
+ def overwrite(*filenames)
28
+ load(*filenames, overwrite: true)
29
+ end
30
+ alias overload overwrite
31
+
32
+ def overwrite!(*filenames)
33
+ load(*filenames, overwrite: true, ignore: false)
34
+ end
35
+ alias overload! overwrite!
36
+
37
+ 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
63
+ end
64
+
65
+ def update(env = {}, overwrite: false)
66
+ changed = {}
67
+ env.each do |key, value|
68
+ key = key.to_s
69
+ next if ENV.key?(key) && overwrite == false
70
+
71
+ if ENV.key?(key) && overwrite == :warn
72
+ warn "Warning: dotenvx not overwriting ENV[#{key.inspect}]"
73
+ next
74
+ end
75
+ unless [true, false, :warn].include?(overwrite)
76
+ raise ArgumentError, "Invalid value for overwrite: #{overwrite.inspect}"
77
+ end
78
+
79
+ ENV[key] = value.to_s
80
+ changed[key] = value.to_s
81
+ end
82
+ changed
83
+ end
84
+
85
+ def require_keys(*keys)
86
+ missing = keys.flatten.map(&:to_s) - ENV.keys
87
+ raise MissingKeys, missing unless missing.empty?
88
+ end
89
+
90
+ private
91
+
92
+ def key_files(path)
93
+ candidates = ["#{path}.keys", File.join(File.dirname(path), ".env.keys")]
94
+ candidates.select { |candidate| File.file?(candidate) }
95
+ .uniq
96
+ end
97
+ end
98
+
99
+ class Environment < Hash
100
+ attr_reader :filename
101
+
102
+ def initialize(filename, values)
103
+ @filename = filename
104
+ super()
105
+ update(values)
106
+ end
6
107
  end
7
108
  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.3
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
data/dotenvx.gemspec DELETED
@@ -1,31 +0,0 @@
1
- require_relative 'lib/dotenvx/version'
2
-
3
- Gem::Specification.new "dotenvx" do |spec|
4
- spec.name = "dotenvx"
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_development_dependency "rake"
29
- spec.add_development_dependency "rspec"
30
- spec.add_development_dependency "byebug"
31
- end
data/lib/dotenvx-rails.rb DELETED
@@ -1 +0,0 @@
1
- require "dotenvx/rails"