renderscreenshot 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 866ece0d8812f0d70751db71d9746530c4e4b0f95a39624f569672b93688137b
4
+ data.tar.gz: a6d705614f59526337dc55e852075ed21030f66e2a71c17a94895db00386993a
5
+ SHA512:
6
+ metadata.gz: f8a11440d6feb0739526fe945732ed2c12e0e0130476c48b6c96d544d98fa70b41c1a9d982e621a42046f42cf0d2bd4b8ffe2d1ef2c618163318a8a4e3b3355d
7
+ data.tar.gz: 11cc424b00ae6727181f580118644a01aa2bb29ddf7554d8a8768e7ebf6bdf410cf862b8728abee757d6d484f30d76fd7f0973ff8e4b31a349249579f1885d48
@@ -0,0 +1,40 @@
1
+ repos:
2
+ # Secret detection - Gitleaks
3
+ - repo: https://github.com/gitleaks/gitleaks
4
+ rev: v8.30.0
5
+ hooks:
6
+ - id: gitleaks
7
+
8
+ # Secret detection - detect-secrets (additional layer)
9
+ - repo: https://github.com/Yelp/detect-secrets
10
+ rev: v1.4.0
11
+ hooks:
12
+ - id: detect-secrets
13
+ args: ['--baseline', '.secrets.baseline']
14
+ exclude: vendor/
15
+
16
+ # General checks
17
+ - repo: https://github.com/pre-commit/pre-commit-hooks
18
+ rev: v6.0.0
19
+ hooks:
20
+ - id: check-added-large-files
21
+ args: ['--maxkb=500']
22
+ - id: check-case-conflict
23
+ - id: check-merge-conflict
24
+ - id: check-yaml
25
+ - id: check-json
26
+ - id: detect-private-key
27
+ - id: end-of-file-fixer
28
+ - id: trailing-whitespace
29
+ - id: no-commit-to-branch
30
+ args: ['--branch', 'main']
31
+
32
+ # Ruby linting
33
+ - repo: local
34
+ hooks:
35
+ - id: rubocop
36
+ name: RuboCop
37
+ entry: bundle exec rubocop --force-exclusion
38
+ language: system
39
+ types: [ruby]
40
+ pass_filenames: true
data/.rubocop.yml ADDED
@@ -0,0 +1,86 @@
1
+ AllCops:
2
+ TargetRubyVersion: 3.0
3
+ NewCops: enable
4
+ SuggestExtensions: false
5
+ Exclude:
6
+ - 'vendor/**/*'
7
+ - 'tmp/**/*'
8
+ - '*.gemspec'
9
+
10
+ Style/Documentation:
11
+ Enabled: false
12
+
13
+ Style/StringLiterals:
14
+ EnforcedStyle: single_quotes
15
+
16
+ Style/FrozenStringLiteralComment:
17
+ Enabled: true
18
+
19
+ # SDK pattern: boolean defaults are common for fluent builders
20
+ Style/OptionalBooleanParameter:
21
+ Enabled: false
22
+
23
+ # SDK pattern: memoization with descriptive names
24
+ Naming/MemoizedInstanceVariableName:
25
+ Enabled: false
26
+
27
+ # Private methods can have short parameter names
28
+ Naming/MethodParameterName:
29
+ Enabled: false
30
+
31
+ # Predicate methods in private scope
32
+ Naming/PredicateMethod:
33
+ Enabled: false
34
+
35
+ # Allow HTTP status codes in test method names
36
+ Naming/VariableNumber:
37
+ CheckMethodNames: false
38
+
39
+ # Allow mutable constants for dynamic values
40
+ Style/MutableConstant:
41
+ Enabled: false
42
+
43
+ # Duplicate branches are intentional for error mapping
44
+ Lint/DuplicateBranch:
45
+ Enabled: false
46
+
47
+ Metrics/BlockLength:
48
+ Exclude:
49
+ - 'test/**/*'
50
+ - 'Rakefile'
51
+
52
+ Metrics/MethodLength:
53
+ Max: 30
54
+ Exclude:
55
+ - 'lib/renderscreenshot/take_options.rb'
56
+ - 'test/**/*'
57
+
58
+ Metrics/AbcSize:
59
+ Max: 50
60
+ Exclude:
61
+ - 'lib/renderscreenshot/take_options.rb'
62
+ - 'test/**/*'
63
+
64
+ Metrics/ClassLength:
65
+ Max: 300
66
+ Exclude:
67
+ - 'lib/renderscreenshot/take_options.rb'
68
+ - 'test/**/*'
69
+
70
+ Metrics/CyclomaticComplexity:
71
+ Max: 15
72
+ Exclude:
73
+ - 'lib/renderscreenshot/take_options.rb'
74
+
75
+ Metrics/PerceivedComplexity:
76
+ Max: 15
77
+ Exclude:
78
+ - 'lib/renderscreenshot/take_options.rb'
79
+
80
+ Metrics/ParameterLists:
81
+ Max: 7
82
+
83
+ Layout/LineLength:
84
+ Max: 120
85
+ Exclude:
86
+ - 'test/**/*'
data/.secrets.baseline ADDED
@@ -0,0 +1,116 @@
1
+ {
2
+ "version": "1.4.0",
3
+ "plugins_used": [
4
+ {
5
+ "name": "ArtifactoryDetector"
6
+ },
7
+ {
8
+ "name": "AWSKeyDetector"
9
+ },
10
+ {
11
+ "name": "AzureStorageKeyDetector"
12
+ },
13
+ {
14
+ "name": "Base64HighEntropyString",
15
+ "limit": 4.5
16
+ },
17
+ {
18
+ "name": "BasicAuthDetector"
19
+ },
20
+ {
21
+ "name": "CloudantDetector"
22
+ },
23
+ {
24
+ "name": "DiscordBotTokenDetector"
25
+ },
26
+ {
27
+ "name": "GitHubTokenDetector"
28
+ },
29
+ {
30
+ "name": "HexHighEntropyString",
31
+ "limit": 3.0
32
+ },
33
+ {
34
+ "name": "IbmCloudIamDetector"
35
+ },
36
+ {
37
+ "name": "IbmCosHmacDetector"
38
+ },
39
+ {
40
+ "name": "JwtTokenDetector"
41
+ },
42
+ {
43
+ "name": "KeywordDetector",
44
+ "keyword_exclude": ""
45
+ },
46
+ {
47
+ "name": "MailchimpDetector"
48
+ },
49
+ {
50
+ "name": "NpmDetector"
51
+ },
52
+ {
53
+ "name": "PrivateKeyDetector"
54
+ },
55
+ {
56
+ "name": "SendGridDetector"
57
+ },
58
+ {
59
+ "name": "SlackDetector"
60
+ },
61
+ {
62
+ "name": "SoftlayerDetector"
63
+ },
64
+ {
65
+ "name": "SquareOAuthDetector"
66
+ },
67
+ {
68
+ "name": "StripeDetector"
69
+ },
70
+ {
71
+ "name": "TwilioKeyDetector"
72
+ }
73
+ ],
74
+ "filters_used": [
75
+ {
76
+ "path": "detect_secrets.filters.allowlist.is_line_allowlisted"
77
+ },
78
+ {
79
+ "path": "detect_secrets.filters.common.is_baseline_file",
80
+ "filename": ".secrets.baseline"
81
+ },
82
+ {
83
+ "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
84
+ "min_level": 2
85
+ },
86
+ {
87
+ "path": "detect_secrets.filters.heuristic.is_indirect_reference"
88
+ },
89
+ {
90
+ "path": "detect_secrets.filters.heuristic.is_likely_id_string"
91
+ },
92
+ {
93
+ "path": "detect_secrets.filters.heuristic.is_lock_file"
94
+ },
95
+ {
96
+ "path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
97
+ },
98
+ {
99
+ "path": "detect_secrets.filters.heuristic.is_potential_uuid"
100
+ },
101
+ {
102
+ "path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
103
+ },
104
+ {
105
+ "path": "detect_secrets.filters.heuristic.is_sequential_string"
106
+ },
107
+ {
108
+ "path": "detect_secrets.filters.heuristic.is_swagger_file"
109
+ },
110
+ {
111
+ "path": "detect_secrets.filters.heuristic.is_templated_secret"
112
+ }
113
+ ],
114
+ "results": {},
115
+ "generated_at": "2025-01-31T00:00:00Z"
116
+ }
data/CHANGELOG.md ADDED
@@ -0,0 +1,59 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [1.0.0] - 2026-02-24
9
+
10
+ ### Added
11
+
12
+ - Initial release of the RenderScreenshot Ruby SDK
13
+ - `Client` class for API interactions
14
+ - `take` - Get screenshot as binary data
15
+ - `take_json` - Get screenshot with JSON metadata
16
+ - `generate_url` - Create signed URLs
17
+ - `batch` - Batch process multiple URLs
18
+ - `batch_advanced` - Batch with per-URL options
19
+ - `get_batch` - Check batch status
20
+ - `presets` - List available presets
21
+ - `preset` - Get specific preset
22
+ - `devices` - List device presets
23
+ - `cache` - Access cache manager
24
+ - `TakeOptions` fluent builder with 58+ methods
25
+ - Viewport: `width`, `height`, `scale`, `mobile`
26
+ - Capture: `full_page`, `element`, `format`, `quality`
27
+ - Wait: `wait_for`, `delay`, `wait_for_selector`, `wait_for_timeout`
28
+ - Presets: `preset`, `device`
29
+ - Blocking: `block_ads`, `block_trackers`, `block_cookie_banners`, `block_chat_widgets`, `block_urls`, `block_resources`
30
+ - Page: `inject_script`, `inject_style`, `click`, `hide`, `remove`
31
+ - Browser: `dark_mode`, `reduced_motion`, `media_type`, `user_agent`, `timezone`, `locale`, `geolocation`
32
+ - Network: `headers`, `cookies`, `auth_basic`, `auth_bearer`, `bypass_csp`
33
+ - Cache: `cache_ttl`, `cache_refresh`
34
+ - PDF: All PDF-specific options
35
+ - Storage: `storage_enabled`, `storage_path`, `storage_acl`
36
+ - `CacheManager` for cache operations
37
+ - `get`, `delete`, `purge`, `purge_url`, `purge_before`, `purge_pattern`
38
+ - `Webhook` module for webhook verification
39
+ - `verify` - HMAC-SHA256 signature verification
40
+ - `parse` - Parse webhook payload
41
+ - `extract_headers` - Extract signature/timestamp from headers
42
+ - Error hierarchy with `retryable?` support
43
+ - `ValidationError`, `AuthenticationError`, `AuthorizationError`
44
+ - `NotFoundError`, `RateLimitError`, `TimeoutError`
45
+ - `ServerError`, `ConnectionError`
46
+ - Global configuration via `RenderScreenshot.configure`
47
+ - Dependabot for automated dependency updates
48
+ - Full test coverage with Minitest (142 tests, 97% coverage)
49
+
50
+ ### Security
51
+
52
+ - Faraday minimum version set to >= 2.12.3 (AIKIDO-2025-10223 vulnerability in earlier versions)
53
+
54
+ ### Requirements
55
+
56
+ - Ruby >= 3.2.0
57
+ - Faraday >= 2.12.3, < 3.0
58
+
59
+ [1.0.0]: https://github.com/Render-Screenshot/rs-ruby/releases/tag/v1.0.0
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ group :development, :test do
8
+ gem 'bundler-audit', '~> 0.9'
9
+ gem 'minitest', '~> 5.20'
10
+ gem 'minitest-reporters', '~> 1.6'
11
+ gem 'rake', '~> 13.0'
12
+ gem 'rubocop', '~> 1.70'
13
+ gem 'simplecov', '~> 0.22', require: false
14
+ gem 'timecop', '~> 0.9'
15
+ gem 'webmock', '~> 3.18'
16
+ gem 'yard', '~> 0.9'
17
+ end
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RenderScreenshot
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,356 @@
1
+ # RenderScreenshot Ruby SDK
2
+
3
+ The official Ruby SDK for [RenderScreenshot](https://renderscreenshot.com) - a developer-friendly screenshot API for capturing web pages programmatically.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'renderscreenshot'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```bash
16
+ bundle install
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```bash
22
+ gem install renderscreenshot
23
+ ```
24
+
25
+ ## Requirements
26
+
27
+ - Ruby 3.0 or higher
28
+
29
+ ## Quick Start
30
+
31
+ ```ruby
32
+ require 'renderscreenshot'
33
+
34
+ # Create a client
35
+ client = RenderScreenshot.client('rs_live_your_api_key')
36
+
37
+ # Take a screenshot
38
+ image_data = client.take(
39
+ RenderScreenshot::TakeOptions
40
+ .url('https://example.com')
41
+ .width(1200)
42
+ .height(630)
43
+ .format('png')
44
+ )
45
+
46
+ # Save to file
47
+ File.binwrite('screenshot.png', image_data)
48
+ ```
49
+
50
+ ## Configuration
51
+
52
+ ### Global Configuration
53
+
54
+ ```ruby
55
+ RenderScreenshot.configure do |config|
56
+ config.base_url = 'https://api.renderscreenshot.com' # Default
57
+ config.timeout = 30 # Default timeout in seconds
58
+ end
59
+ ```
60
+
61
+ ### Per-Client Configuration
62
+
63
+ ```ruby
64
+ client = RenderScreenshot.client(
65
+ 'rs_live_your_api_key',
66
+ base_url: 'https://custom.api.com',
67
+ timeout: 60
68
+ )
69
+ ```
70
+
71
+ ## Usage
72
+
73
+ ### Taking Screenshots
74
+
75
+ #### Binary Response
76
+
77
+ ```ruby
78
+ # Get screenshot as binary data
79
+ image_data = client.take(
80
+ RenderScreenshot::TakeOptions
81
+ .url('https://example.com')
82
+ .preset('og_card')
83
+ )
84
+ ```
85
+
86
+ #### JSON Response (with metadata)
87
+
88
+ ```ruby
89
+ # Get screenshot URL and metadata
90
+ response = client.take_json(
91
+ RenderScreenshot::TakeOptions
92
+ .url('https://example.com')
93
+ .preset('og_card')
94
+ )
95
+
96
+ puts response['image']['url'] # CDN URL
97
+ puts response['image']['width'] # 1200
98
+ puts response['image']['height'] # 630
99
+ puts response['cache']['key'] # Cache key for later reference
100
+ ```
101
+
102
+ ### Screenshot Options
103
+
104
+ The `TakeOptions` class provides a fluent interface for configuring screenshots:
105
+
106
+ ```ruby
107
+ options = RenderScreenshot::TakeOptions
108
+ .url('https://example.com')
109
+ # Viewport
110
+ .width(1920)
111
+ .height(1080)
112
+ .scale(2)
113
+ .mobile(true)
114
+ # Capture
115
+ .full_page
116
+ .element('#main-content')
117
+ .format('webp')
118
+ .quality(90)
119
+ # Wait conditions
120
+ .wait_for('networkidle')
121
+ .delay(500)
122
+ .wait_for_selector('.loaded')
123
+ # Blocking
124
+ .block_ads
125
+ .block_trackers
126
+ .block_cookie_banners
127
+ # Browser emulation
128
+ .dark_mode
129
+ .timezone('America/New_York')
130
+ .locale('en-US')
131
+ ```
132
+
133
+ ### Using Presets
134
+
135
+ ```ruby
136
+ # Social card presets
137
+ options = RenderScreenshot::TakeOptions
138
+ .url('https://example.com')
139
+ .preset('og_card') # 1200x630 for Open Graph
140
+
141
+ # Device presets
142
+ options = RenderScreenshot::TakeOptions
143
+ .url('https://example.com')
144
+ .device('iphone_14_pro')
145
+ ```
146
+
147
+ ### PDF Generation
148
+
149
+ ```ruby
150
+ options = RenderScreenshot::TakeOptions
151
+ .url('https://example.com')
152
+ .format('pdf')
153
+ .pdf_paper_size('a4')
154
+ .pdf_landscape
155
+ .pdf_print_background
156
+ .pdf_margin('1cm')
157
+
158
+ pdf_data = client.take(options)
159
+ File.binwrite('document.pdf', pdf_data)
160
+ ```
161
+
162
+ ### Batch Processing
163
+
164
+ ```ruby
165
+ # Simple batch (same options for all URLs)
166
+ response = client.batch(
167
+ ['https://example1.com', 'https://example2.com', 'https://example3.com'],
168
+ options: RenderScreenshot::TakeOptions.url('').preset('og_card')
169
+ )
170
+
171
+ response['results'].each do |result|
172
+ puts "#{result['url']}: #{result['status']}"
173
+ end
174
+
175
+ # Advanced batch (per-URL options)
176
+ response = client.batch_advanced([
177
+ { url: 'https://example1.com', options: { preset: 'og_card' } },
178
+ { url: 'https://example2.com', options: { preset: 'twitter_card' } }
179
+ ])
180
+ ```
181
+
182
+ ### Signed URLs
183
+
184
+ Generate signed URLs for client-side use without exposing your API key:
185
+
186
+ ```ruby
187
+ options = RenderScreenshot::TakeOptions
188
+ .url('https://example.com')
189
+ .preset('og_card')
190
+
191
+ # URL expires in 24 hours
192
+ signed_url = client.generate_url(options, expires_at: Time.now + 86400)
193
+
194
+ # Use in HTML
195
+ # <img src="#{signed_url}" />
196
+ ```
197
+
198
+ ### Cache Management
199
+
200
+ ```ruby
201
+ cache = client.cache
202
+
203
+ # Get cached screenshot
204
+ data = cache.get('cache_key_123')
205
+
206
+ # Delete cached entry
207
+ cache.delete('cache_key_123')
208
+
209
+ # Bulk purge
210
+ cache.purge(['key1', 'key2', 'key3'])
211
+
212
+ # Purge by URL pattern
213
+ cache.purge_url('https://example.com/*')
214
+
215
+ # Purge by date
216
+ cache.purge_before(Time.now - 86400 * 7) # Older than 7 days
217
+
218
+ # Purge by storage path pattern
219
+ cache.purge_pattern('screenshots/2024/01/*')
220
+ ```
221
+
222
+ ### Presets and Devices
223
+
224
+ ```ruby
225
+ # List all presets
226
+ presets = client.presets
227
+ presets.each { |p| puts "#{p['id']}: #{p['name']}" }
228
+
229
+ # Get specific preset
230
+ preset = client.preset('og_card')
231
+
232
+ # List all devices
233
+ devices = client.devices
234
+ devices.each { |d| puts "#{d['id']}: #{d['name']} (#{d['width']}x#{d['height']})" }
235
+ ```
236
+
237
+ ### Webhook Verification
238
+
239
+ ```ruby
240
+ # In your webhook endpoint
241
+ def webhook_handler(request)
242
+ payload = request.body.read
243
+ signature, timestamp = RenderScreenshot::Webhook.extract_headers(request.headers)
244
+
245
+ unless RenderScreenshot::Webhook.verify(
246
+ payload: payload,
247
+ signature: signature,
248
+ timestamp: timestamp,
249
+ secret: ENV['WEBHOOK_SECRET']
250
+ )
251
+ return [401, 'Invalid signature']
252
+ end
253
+
254
+ event = RenderScreenshot::Webhook.parse(payload)
255
+
256
+ case event[:event]
257
+ when 'screenshot.completed'
258
+ handle_screenshot_completed(event[:data])
259
+ when 'screenshot.failed'
260
+ handle_screenshot_failed(event[:data])
261
+ when 'batch.completed'
262
+ handle_batch_completed(event[:data])
263
+ end
264
+
265
+ [200, 'OK']
266
+ end
267
+ ```
268
+
269
+ ## Error Handling
270
+
271
+ All API errors inherit from `RenderScreenshot::Error`:
272
+
273
+ ```ruby
274
+ begin
275
+ client.take(RenderScreenshot::TakeOptions.url('https://example.com'))
276
+ rescue RenderScreenshot::ValidationError => e
277
+ puts "Invalid request: #{e.message}"
278
+ rescue RenderScreenshot::AuthenticationError => e
279
+ puts "Auth failed: #{e.message}"
280
+ rescue RenderScreenshot::RateLimitError => e
281
+ puts "Rate limited. Retry after #{e.retry_after} seconds"
282
+ sleep(e.retry_after) if e.retry_after
283
+ retry
284
+ rescue RenderScreenshot::TimeoutError => e
285
+ puts "Request timed out" if e.retryable?
286
+ rescue RenderScreenshot::ServerError => e
287
+ puts "Server error: #{e.message}"
288
+ retry if e.retryable?
289
+ rescue RenderScreenshot::ConnectionError => e
290
+ puts "Connection failed: #{e.message}"
291
+ end
292
+ ```
293
+
294
+ Error properties:
295
+ - `http_status` - HTTP status code
296
+ - `code` - Error code from API
297
+ - `message` - Human-readable message
298
+ - `request_id` - Request ID for support
299
+ - `retry_after` - Seconds to wait (rate limits)
300
+ - `retryable?` - Whether the error can be retried
301
+
302
+ ## Complete Options Reference
303
+
304
+ | Category | Methods |
305
+ |----------|---------|
306
+ | **Viewport** | `width`, `height`, `scale`, `mobile` |
307
+ | **Capture** | `full_page`, `element`, `format`, `quality` |
308
+ | **Wait** | `wait_for`, `delay`, `wait_for_selector`, `wait_for_timeout` |
309
+ | **Preset** | `preset`, `device` |
310
+ | **Blocking** | `block_ads`, `block_trackers`, `block_cookie_banners`, `block_chat_widgets`, `block_urls`, `block_resources` |
311
+ | **Page** | `inject_script`, `inject_style`, `click`, `hide`, `remove` |
312
+ | **Browser** | `dark_mode`, `reduced_motion`, `media_type`, `user_agent`, `timezone`, `locale`, `geolocation` |
313
+ | **Network** | `headers`, `cookies`, `auth_basic`, `auth_bearer`, `bypass_csp` |
314
+ | **Cache** | `cache_ttl`, `cache_refresh` |
315
+ | **PDF** | `pdf_paper_size`, `pdf_width`, `pdf_height`, `pdf_landscape`, `pdf_margin`, `pdf_margin_top`, `pdf_margin_right`, `pdf_margin_bottom`, `pdf_margin_left`, `pdf_scale`, `pdf_print_background`, `pdf_page_ranges`, `pdf_header`, `pdf_footer`, `pdf_fit_one_page`, `pdf_prefer_css_page_size` |
316
+ | **Storage** | `storage_enabled`, `storage_path`, `storage_acl` |
317
+
318
+ ## Development
319
+
320
+ After checking out the repo:
321
+
322
+ ```bash
323
+ bundle install
324
+ bundle exec rake test # Run tests
325
+ bundle exec rubocop # Run linter
326
+ bundle exec rake # Run both
327
+ ```
328
+
329
+ ### Pre-commit Hooks
330
+
331
+ This project uses pre-commit hooks to prevent secrets from being committed:
332
+
333
+ ```bash
334
+ # Install pre-commit (if not already installed)
335
+ brew install pre-commit # macOS
336
+ pip install pre-commit # or via pip
337
+
338
+ # Install the hooks
339
+ pre-commit install
340
+
341
+ # Run manually on all files
342
+ pre-commit run --all-files
343
+ ```
344
+
345
+ The hooks include:
346
+ - **Gitleaks** - Scans for secrets and API keys
347
+ - **detect-private-key** - Prevents committing private keys
348
+ - **check-added-large-files** - Prevents large files (>500KB)
349
+
350
+ ## Contributing
351
+
352
+ Bug reports and pull requests are welcome on GitHub at https://github.com/renderscreenshot/renderscreenshot-ruby.
353
+
354
+ ## License
355
+
356
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).