opdotenv 1.0.3 → 1.0.5
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 +10 -0
- data/README.md +20 -2
- data/lib/opdotenv/anyway_loader.rb +22 -10
- data/lib/opdotenv/connect_api_client.rb +24 -22
- data/lib/opdotenv/loader.rb +14 -13
- data/lib/opdotenv/parsers/dotenv_parser.rb +26 -19
- data/lib/opdotenv/source_parser.rb +5 -6
- data/lib/opdotenv/version.rb +1 -1
- metadata +104 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 681b84ce04a08dea4ef5bb4645ecf47071922f63288f5d6b26fcc558d2b181fd
|
|
4
|
+
data.tar.gz: d6706fb654448e9d4efa404d373d54d5f9ef5b5f5553a73c5335c28b6a7f9508
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 43741125e5322870b2f11d57d6b7c2a1d6ea695d69209b93938a546b5ac44f785064b21cdaf2ca6ead5a232b5e36d6d0035f90a4fa9e2f668af0b53aa1c34f8d
|
|
7
|
+
data.tar.gz: 0b1855b8e5804b98dd8bf4936a218a69bfb415073e38b78f25102744b979674f9afda9baec9570c4dbf67deeee46e226be78073ab9a51105f9e989e9a2f8a522
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 1.0.5 (2026-07-06)
|
|
4
|
+
|
|
5
|
+
- Improve `OPDOTENV_DEBUG` field summary logging in Anyway Config integration to build messages only when Rails debug logging is active
|
|
6
|
+
- Skip field summary debug output when a 1Password item has no fields
|
|
7
|
+
|
|
8
|
+
## 1.0.4 (2026-07-05)
|
|
9
|
+
|
|
10
|
+
- Move `railties` from runtime to development dependency in gemspec
|
|
11
|
+
- Non-Rails installs no longer pull in the Rails gem tree; Rails apps already provide `railties`
|
|
12
|
+
|
|
3
13
|
## 1.0.3 (2025-11-07)
|
|
4
14
|
|
|
5
15
|
- Fix exit code handling in OpClient by using Open3.capture2e instead of IO.popen for reliable process status
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# opdotenv
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/opdotenv) [](https://github.com/amkisko/opdotenv.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/opdotenv.rb)
|
|
3
|
+
[](https://badge.fury.io/rb/opdotenv) [](https://github.com/amkisko/opdotenv.rb/actions/workflows/test.yml) [](https://codecov.io/gh/amkisko/opdotenv.rb) [](https://sonarcloud.io/summary/new_code?id=amkisko_opdotenv.rb)
|
|
4
4
|
|
|
5
5
|
Load environment variables from 1Password using the `op` CLI or 1Password Connect Server API. Supports dotenv, JSON, and YAML formats.
|
|
6
6
|
|
|
@@ -350,6 +350,24 @@ bundle exec rbs validate
|
|
|
350
350
|
bundle exec standardrb --fix
|
|
351
351
|
```
|
|
352
352
|
|
|
353
|
+
## Contributing
|
|
354
|
+
|
|
355
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/amkisko/opdotenv.rb.
|
|
356
|
+
|
|
357
|
+
Contribution policy:
|
|
358
|
+
- New features are not necessarily added to the gem
|
|
359
|
+
- Pull request should have test coverage for affected parts
|
|
360
|
+
- Pull request should have changelog entry
|
|
361
|
+
|
|
362
|
+
Review policy:
|
|
363
|
+
- It might take up to 2 calendar weeks to review and merge critical fixes
|
|
364
|
+
- It might take up to 6 calendar months to review and merge pull request
|
|
365
|
+
- It might take up to 1 calendar year to review an issue
|
|
366
|
+
|
|
367
|
+
## Security
|
|
368
|
+
|
|
369
|
+
If you discover a security vulnerability, please report it responsibly. See [SECURITY.md](SECURITY.md) for details.
|
|
370
|
+
|
|
353
371
|
## License
|
|
354
372
|
|
|
355
|
-
MIT
|
|
373
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
@@ -79,26 +79,38 @@ module Opdotenv
|
|
|
79
79
|
def log_available_fields(original_data, env_prefix, matched_data)
|
|
80
80
|
return unless ENV["OPDOTENV_DEBUG"] == "true"
|
|
81
81
|
return unless defined?(Rails) && Rails.logger
|
|
82
|
+
return if original_data.keys.empty?
|
|
82
83
|
|
|
83
84
|
prefix_upper = env_prefix.upcase
|
|
84
85
|
prefix_with_underscore = "#{prefix_upper}_"
|
|
85
|
-
|
|
86
86
|
available_fields = original_data.keys.map(&:to_s)
|
|
87
87
|
matched_fields = matched_data.keys.map(&:to_s)
|
|
88
|
+
unmatched = unmatched_fields(available_fields, prefix_upper, prefix_with_underscore)
|
|
89
|
+
|
|
90
|
+
log_field_summary(available_fields, matched_fields, env_prefix, prefix_with_underscore)
|
|
91
|
+
log_unmatched_fields(unmatched, prefix_with_underscore) if unmatched.any?
|
|
92
|
+
end
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
def unmatched_fields(available_fields, prefix_upper, prefix_with_underscore)
|
|
95
|
+
available_fields.reject do |field|
|
|
91
96
|
field_upper = field.to_s.upcase
|
|
92
97
|
field_upper.start_with?(prefix_with_underscore) || field_upper == prefix_upper
|
|
93
98
|
end
|
|
99
|
+
end
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
def log_field_summary(available_fields, matched_fields, env_prefix, prefix_with_underscore)
|
|
102
|
+
Rails.logger.debug { "[opdotenv] Available fields from 1Password: #{available_fields.join(", ")}" }
|
|
103
|
+
Rails.logger.debug do
|
|
104
|
+
"[opdotenv] Matched fields for #{env_prefix} (prefixed with #{prefix_with_underscore}, case-insensitive): #{matched_fields.join(", ")}"
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def log_unmatched_fields(unmatched, prefix_with_underscore)
|
|
109
|
+
Rails.logger.debug do
|
|
110
|
+
"[opdotenv] Unmatched fields (must be prefixed with #{prefix_with_underscore}, case-insensitive): #{unmatched.join(", ")}"
|
|
111
|
+
end
|
|
112
|
+
Rails.logger.debug do
|
|
113
|
+
"[opdotenv] To use these fields, rename them in 1Password to include the #{prefix_with_underscore} prefix"
|
|
102
114
|
end
|
|
103
115
|
end
|
|
104
116
|
end
|
|
@@ -86,28 +86,7 @@ module Opdotenv
|
|
|
86
86
|
existing = item_by_title_in_vault(vault_id, item)
|
|
87
87
|
|
|
88
88
|
if existing
|
|
89
|
-
|
|
90
|
-
fields_array = fields.map do |k, v|
|
|
91
|
-
existing_field = existing["fields"]&.find { |f| f["label"] == k.to_s }
|
|
92
|
-
if existing_field
|
|
93
|
-
{
|
|
94
|
-
"op" => "replace",
|
|
95
|
-
"path" => "/fields/#{existing_field["id"]}/value",
|
|
96
|
-
"value" => v.to_s
|
|
97
|
-
}
|
|
98
|
-
else
|
|
99
|
-
{
|
|
100
|
-
"op" => "add",
|
|
101
|
-
"path" => "/fields",
|
|
102
|
-
"value" => {
|
|
103
|
-
"type" => "CONCEALED",
|
|
104
|
-
"label" => k.to_s,
|
|
105
|
-
"value" => v.to_s
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
|
|
89
|
+
fields_array = build_patch_fields(existing, fields)
|
|
111
90
|
api_request(:patch, "/v1/vaults/#{vault_id}/items/#{existing["id"]}", fields_array)
|
|
112
91
|
else
|
|
113
92
|
# Create new item
|
|
@@ -132,6 +111,29 @@ module Opdotenv
|
|
|
132
111
|
|
|
133
112
|
private
|
|
134
113
|
|
|
114
|
+
def build_patch_fields(existing, fields)
|
|
115
|
+
fields.map do |key, value|
|
|
116
|
+
existing_field = existing["fields"]&.find { |field| field["label"] == key.to_s }
|
|
117
|
+
if existing_field
|
|
118
|
+
{
|
|
119
|
+
"op" => "replace",
|
|
120
|
+
"path" => "/fields/#{existing_field["id"]}/value",
|
|
121
|
+
"value" => value.to_s
|
|
122
|
+
}
|
|
123
|
+
else
|
|
124
|
+
{
|
|
125
|
+
"op" => "add",
|
|
126
|
+
"path" => "/fields",
|
|
127
|
+
"value" => {
|
|
128
|
+
"type" => "CONCEALED",
|
|
129
|
+
"label" => key.to_s,
|
|
130
|
+
"value" => value.to_s
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
135
137
|
def parse_path(path)
|
|
136
138
|
# op://Vault/Item or connect://Vault/Item
|
|
137
139
|
match = path.match(/\A(?:op|connect):\/\/([^\/]+)\/([^\/]+)(?:\/(.+))?\z/)
|
data/lib/opdotenv/loader.rb
CHANGED
|
@@ -25,24 +25,25 @@ module Opdotenv
|
|
|
25
25
|
raw_json = client.item_get(item, vault: vault)
|
|
26
26
|
item_hash = parse_json_safe(raw_json)
|
|
27
27
|
|
|
28
|
-
# Only process fields from the 1Password item, filter out empty values
|
|
29
28
|
item_hash["fields"]&.each_with_object({}) do |field, env_data|
|
|
30
|
-
|
|
31
|
-
next unless
|
|
29
|
+
entry = field_env_entry(field)
|
|
30
|
+
next unless entry
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
env_data[entry[:key]] = entry[:value]
|
|
33
|
+
end || {}
|
|
34
|
+
end
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
def self.field_env_entry(field)
|
|
37
|
+
return nil unless field.is_a?(Hash)
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
label = (field["label"] || field["id"])&.strip
|
|
40
|
+
return nil if label.nil? || label.empty?
|
|
41
|
+
return nil if field["purpose"] == NOTES_PURPOSE
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
value = field["value"]
|
|
44
|
+
return nil if value.nil? || (value.is_a?(String) && value.strip.empty?)
|
|
45
|
+
|
|
46
|
+
{key: label.to_s, value: value.to_s}
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
def self.build_field_path(path, field_name)
|
|
@@ -2,26 +2,33 @@ module Opdotenv
|
|
|
2
2
|
module Parsers
|
|
3
3
|
class DotenvParser
|
|
4
4
|
def self.parse(text)
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
5
|
+
text.to_s.each_line.with_object({}) do |line, env|
|
|
6
|
+
entry = parse_line(line)
|
|
7
|
+
next unless entry
|
|
8
|
+
|
|
9
|
+
env[entry[:key]] = entry[:value]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.parse_line(line)
|
|
14
|
+
line = line.strip
|
|
15
|
+
return nil if line.empty? || line.start_with?("#")
|
|
16
|
+
|
|
17
|
+
line = line.sub(/^export\s+/, "")
|
|
18
|
+
match = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\z/)
|
|
19
|
+
return nil unless match
|
|
20
|
+
|
|
21
|
+
{key: match[1], value: unquote_value(match[2])}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.unquote_value(raw)
|
|
25
|
+
if raw.start_with?("\"") && raw.end_with?("\"")
|
|
26
|
+
raw[1..-2].gsub('\\"', '"')
|
|
27
|
+
elsif raw.start_with?("'") && raw.end_with?("'")
|
|
28
|
+
raw[1..-2]
|
|
29
|
+
else
|
|
30
|
+
raw
|
|
23
31
|
end
|
|
24
|
-
env
|
|
25
32
|
end
|
|
26
33
|
end
|
|
27
34
|
end
|
|
@@ -19,18 +19,17 @@ module Opdotenv
|
|
|
19
19
|
|
|
20
20
|
path = source.to_s
|
|
21
21
|
_, item = parse_op_path(path)
|
|
22
|
-
|
|
23
|
-
# Extract item name and potential field name
|
|
24
|
-
# Handle paths like "op://Vault/Item" or "op://Vault/Item Name/field"
|
|
25
22
|
item_parts = item.split("/")
|
|
26
23
|
item_name = item_parts.first
|
|
27
24
|
field_name = (item_parts.length > 1) ? item_parts[1] : nil
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
build_parsed_source(path, item_name, field_name)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def build_parsed_source(path, item_name, field_name)
|
|
30
30
|
if field_name
|
|
31
31
|
field_type = FormatInferrer.infer_from_name(field_name)
|
|
32
32
|
{path: path, field_name: field_name, field_type: field_type}
|
|
33
|
-
# If no field name in path, infer from item name patterns
|
|
34
33
|
elsif (field_type = FormatInferrer.infer_from_name(item_name))
|
|
35
34
|
{path: path, field_name: NOTES_PLAIN_FIELD, field_type: field_type}
|
|
36
35
|
else
|
|
@@ -66,6 +65,6 @@ module Opdotenv
|
|
|
66
65
|
}
|
|
67
66
|
end
|
|
68
67
|
|
|
69
|
-
module_function :normalize_hash
|
|
68
|
+
module_function :normalize_hash, :build_parsed_source
|
|
70
69
|
end
|
|
71
70
|
end
|
data/lib/opdotenv/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: opdotenv
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrei Makarov
|
|
@@ -19,7 +19,7 @@ dependencies:
|
|
|
19
19
|
- - "<"
|
|
20
20
|
- !ruby/object:Gem::Version
|
|
21
21
|
version: '9.0'
|
|
22
|
-
type: :
|
|
22
|
+
type: :development
|
|
23
23
|
prerelease: false
|
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
25
25
|
requirements:
|
|
@@ -57,6 +57,20 @@ dependencies:
|
|
|
57
57
|
- - "~>"
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
59
|
version: '3'
|
|
60
|
+
- !ruby/object:Gem::Dependency
|
|
61
|
+
name: polyrun
|
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - "~>"
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: 1.5.0
|
|
67
|
+
type: :development
|
|
68
|
+
prerelease: false
|
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - "~>"
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: 1.5.0
|
|
60
74
|
- !ruby/object:Gem::Dependency
|
|
61
75
|
name: simplecov
|
|
62
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -105,28 +119,112 @@ dependencies:
|
|
|
105
119
|
requirements:
|
|
106
120
|
- - "~>"
|
|
107
121
|
- !ruby/object:Gem::Version
|
|
108
|
-
version: '1'
|
|
122
|
+
version: '1.52'
|
|
109
123
|
type: :development
|
|
110
124
|
prerelease: false
|
|
111
125
|
version_requirements: !ruby/object:Gem::Requirement
|
|
112
126
|
requirements:
|
|
113
127
|
- - "~>"
|
|
114
128
|
- !ruby/object:Gem::Version
|
|
115
|
-
version: '1'
|
|
129
|
+
version: '1.52'
|
|
130
|
+
- !ruby/object:Gem::Dependency
|
|
131
|
+
name: standard-custom
|
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
|
133
|
+
requirements:
|
|
134
|
+
- - "~>"
|
|
135
|
+
- !ruby/object:Gem::Version
|
|
136
|
+
version: '1.0'
|
|
137
|
+
type: :development
|
|
138
|
+
prerelease: false
|
|
139
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
140
|
+
requirements:
|
|
141
|
+
- - "~>"
|
|
142
|
+
- !ruby/object:Gem::Version
|
|
143
|
+
version: '1.0'
|
|
116
144
|
- !ruby/object:Gem::Dependency
|
|
117
145
|
name: standard-performance
|
|
118
146
|
requirement: !ruby/object:Gem::Requirement
|
|
119
147
|
requirements:
|
|
120
148
|
- - "~>"
|
|
121
149
|
- !ruby/object:Gem::Version
|
|
122
|
-
version: '1'
|
|
150
|
+
version: '1.8'
|
|
151
|
+
type: :development
|
|
152
|
+
prerelease: false
|
|
153
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
154
|
+
requirements:
|
|
155
|
+
- - "~>"
|
|
156
|
+
- !ruby/object:Gem::Version
|
|
157
|
+
version: '1.8'
|
|
158
|
+
- !ruby/object:Gem::Dependency
|
|
159
|
+
name: standard-rails
|
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
|
161
|
+
requirements:
|
|
162
|
+
- - "~>"
|
|
163
|
+
- !ruby/object:Gem::Version
|
|
164
|
+
version: '1.5'
|
|
165
|
+
type: :development
|
|
166
|
+
prerelease: false
|
|
167
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
168
|
+
requirements:
|
|
169
|
+
- - "~>"
|
|
170
|
+
- !ruby/object:Gem::Version
|
|
171
|
+
version: '1.5'
|
|
172
|
+
- !ruby/object:Gem::Dependency
|
|
173
|
+
name: standard-rspec
|
|
174
|
+
requirement: !ruby/object:Gem::Requirement
|
|
175
|
+
requirements:
|
|
176
|
+
- - "~>"
|
|
177
|
+
- !ruby/object:Gem::Version
|
|
178
|
+
version: '0.3'
|
|
179
|
+
type: :development
|
|
180
|
+
prerelease: false
|
|
181
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
182
|
+
requirements:
|
|
183
|
+
- - "~>"
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: '0.3'
|
|
186
|
+
- !ruby/object:Gem::Dependency
|
|
187
|
+
name: rubocop-rails
|
|
188
|
+
requirement: !ruby/object:Gem::Requirement
|
|
189
|
+
requirements:
|
|
190
|
+
- - "~>"
|
|
191
|
+
- !ruby/object:Gem::Version
|
|
192
|
+
version: '2.33'
|
|
193
|
+
type: :development
|
|
194
|
+
prerelease: false
|
|
195
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
196
|
+
requirements:
|
|
197
|
+
- - "~>"
|
|
198
|
+
- !ruby/object:Gem::Version
|
|
199
|
+
version: '2.33'
|
|
200
|
+
- !ruby/object:Gem::Dependency
|
|
201
|
+
name: rubocop-rspec
|
|
202
|
+
requirement: !ruby/object:Gem::Requirement
|
|
203
|
+
requirements:
|
|
204
|
+
- - "~>"
|
|
205
|
+
- !ruby/object:Gem::Version
|
|
206
|
+
version: '3.8'
|
|
207
|
+
type: :development
|
|
208
|
+
prerelease: false
|
|
209
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
210
|
+
requirements:
|
|
211
|
+
- - "~>"
|
|
212
|
+
- !ruby/object:Gem::Version
|
|
213
|
+
version: '3.8'
|
|
214
|
+
- !ruby/object:Gem::Dependency
|
|
215
|
+
name: rubocop-thread_safety
|
|
216
|
+
requirement: !ruby/object:Gem::Requirement
|
|
217
|
+
requirements:
|
|
218
|
+
- - "~>"
|
|
219
|
+
- !ruby/object:Gem::Version
|
|
220
|
+
version: '0.7'
|
|
123
221
|
type: :development
|
|
124
222
|
prerelease: false
|
|
125
223
|
version_requirements: !ruby/object:Gem::Requirement
|
|
126
224
|
requirements:
|
|
127
225
|
- - "~>"
|
|
128
226
|
- !ruby/object:Gem::Version
|
|
129
|
-
version: '
|
|
227
|
+
version: '0.7'
|
|
130
228
|
- !ruby/object:Gem::Dependency
|
|
131
229
|
name: appraisal
|
|
132
230
|
requirement: !ruby/object:Gem::Requirement
|