webpacker_uploader 0.2.1 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +197 -10
- data/.yardopts +8 -0
- data/CHANGELOG.md +34 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +55 -41
- data/README.md +56 -15
- data/Rakefile +13 -0
- data/lib/webpacker_uploader.rb +15 -3
- data/lib/webpacker_uploader/configuration.rb +43 -0
- data/lib/webpacker_uploader/instance.rb +44 -10
- data/lib/webpacker_uploader/manifest.rb +3 -3
- data/lib/webpacker_uploader/mime.rb +6 -0
- data/lib/webpacker_uploader/providers/aws.rb +82 -11
- data/lib/webpacker_uploader/version.rb +3 -1
- data/test/configuration_test.rb +87 -0
- data/test/manifest_test.rb +27 -0
- data/test/mime_test.rb +14 -0
- data/test/providers/aws_test.rb +16 -0
- data/test/test_app/config/webpacker.yml +103 -0
- data/test/test_app/public/packs/manifest.json +33 -0
- data/test/test_helper.rb +35 -0
- data/test/webpacker_uploader_test.rb +32 -0
- data/webpacker_uploader.gemspec +7 -9
- metadata +57 -13
- data/.github/workflows/rubocop.yml +0 -35
- data/.github/workflows/ruby.yml +0 -39
- data/bin/console +0 -14
- data/bin/setup +0 -8
data/README.md
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
[![Ruby tests](https://img.shields.io/github/workflow/status/tlatsas/webpacker_uploader/Ruby%20tests?style=flat-square)](https://github.com/tlatsas/webpacker_uploader/actions)
|
4
4
|
[![RuboCop](https://img.shields.io/github/workflow/status/tlatsas/webpacker_uploader/RuboCop?label=rubocop&style=flat-square)](https://github.com/tlatsas/webpacker_uploader/actions)
|
5
5
|
[![Gem](https://img.shields.io/gem/v/webpacker_uploader?style=flat-square)](https://rubygems.org/gems/webpacker_uploader)
|
6
|
+
[![Yard Docs](http://img.shields.io/badge/yard-docs-blue.svg?style=flat-square)](https://www.rubydoc.info/gems/webpacker_uploader)
|
6
7
|
|
7
8
|
Webpacker uploader provides an easy way to upload your assets to AWS S3.
|
8
9
|
It knows which files to upload by reading the `manifest.json` file.
|
@@ -15,14 +16,14 @@ S3 + CDN, outside of your Rails application.
|
|
15
16
|
Add this line to your application's Gemfile:
|
16
17
|
|
17
18
|
```ruby
|
18
|
-
gem "webpacker_uploader"
|
19
|
+
gem "webpacker_uploader", require: false
|
19
20
|
```
|
20
21
|
|
21
|
-
|
22
|
+
and run:
|
22
23
|
|
23
24
|
$ bundle install
|
24
25
|
|
25
|
-
|
26
|
+
or:
|
26
27
|
|
27
28
|
$ gem install webpacker_uploader
|
28
29
|
|
@@ -34,6 +35,8 @@ gem "aws-sdk-s3", require: false
|
|
34
35
|
|
35
36
|
## Usage
|
36
37
|
|
38
|
+
Usually, in a Rake task you would add the following to upload the assets to AWS S3:
|
39
|
+
|
37
40
|
```ruby
|
38
41
|
require "webpacker_uploader"
|
39
42
|
require "webpacker_uploader/providers/aws"
|
@@ -89,18 +92,6 @@ provider_options = {
|
|
89
92
|
provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
90
93
|
```
|
91
94
|
|
92
|
-
### Ignore files
|
93
|
-
|
94
|
-
The uploader can be configured to skip certain files based on the file extension.
|
95
|
-
By default `.map` files are excluded. This can be configured through the `ignored_extensions` attribute.
|
96
|
-
In order to upload everything pass an empty array.
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
# skip uploading images
|
100
|
-
WebpackerUploader.ignored_extensions = [".png", ".jpg", ".webp"]
|
101
|
-
WebpackerUploader.upload!(provider)
|
102
|
-
```
|
103
|
-
|
104
95
|
### Prefix remote files
|
105
96
|
|
106
97
|
Uploaded files can be prefixed by setting the `prefix` parameter during upload:
|
@@ -112,6 +103,43 @@ WebpackerUploader.upload!(provider, prefix: "assets")
|
|
112
103
|
This will prefix all remote file paths with `assets` so instead of storing `packs/application-dd6b1cd38bfa093df600.css` it
|
113
104
|
will store `assets/packs/application-dd6b1cd38bfa093df600.css`.
|
114
105
|
|
106
|
+
### Configuration
|
107
|
+
|
108
|
+
WebpackerUploader currently supports the following configuration options:
|
109
|
+
|
110
|
+
| option | description | default value |
|
111
|
+
|----------------------|--------------------------------------------------------------|---------------------------------------|
|
112
|
+
| ignored_extensions | Which files uploader should skip based on the file extension | [] |
|
113
|
+
| logger | The logger to use for logging | ActiveSupport::Logger.new(STDOUT) |
|
114
|
+
| log_output | Log output as we upload files | true |
|
115
|
+
| public_manifest_path | The webpack manifest path | Webpacker.config.public_manifest_path |
|
116
|
+
| public_path | The webpack public output path | Webpacker.config.public_path |
|
117
|
+
|
118
|
+
It can be configured using a block:
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
WebpackerUploader.configure do |config|
|
122
|
+
config.ignored_extensions = [".png", ".jpg", ".webp"]
|
123
|
+
config.log_output = false
|
124
|
+
config.public_manifest_path = "path/to/manifest.json"
|
125
|
+
config.public_path = "path/to/public/dir"
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
or directly:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
WebpackerUploader.config.log_output = false
|
133
|
+
```
|
134
|
+
|
135
|
+
The uploader used to skip `.map` files by default. This has changed (see [CHANGELOG.md](https://github.com/tlatsas/webpacker_uploader/blob/main/CHANGELOG.md))
|
136
|
+
and everything is uploaded by default now. To retain the previous functionality use:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
# skip uploading map files
|
140
|
+
WebpackerUploader.config.ignored_extensions = [".map"]
|
141
|
+
```
|
142
|
+
|
115
143
|
## Development
|
116
144
|
|
117
145
|
After checking out the repo, run `bin/setup` to install dependencies.
|
@@ -120,6 +148,19 @@ interactive prompt that will allow you to experiment.
|
|
120
148
|
|
121
149
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
122
150
|
|
151
|
+
## Integration tests
|
152
|
+
|
153
|
+
This gem also provides an integration test suite. It runs using [localstack](https://github.com/localstack/localstack/).
|
154
|
+
To run the integration tests, install docker on your system and spin up a localstack container running the s3 service.
|
155
|
+
|
156
|
+
```shell
|
157
|
+
docker compose -f "integration/docker-compose.yml" up --detach
|
158
|
+
```
|
159
|
+
|
160
|
+
To run the tests, use `rake test:integration`.
|
161
|
+
|
162
|
+
To stop the container once done run: `docker-compose -f "integration/docker-compose.yml" down`
|
163
|
+
|
123
164
|
## Contributing
|
124
165
|
|
125
166
|
Bug reports and pull requests are welcome on GitHub at https://github.com/tlatsas/webpacker_uploader.
|
data/Rakefile
CHANGED
@@ -1,10 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "bundler/gem_tasks"
|
2
4
|
require "rake/testtask"
|
3
5
|
|
6
|
+
require "yard"
|
7
|
+
YARD::Rake::YardocTask.new
|
8
|
+
|
4
9
|
Rake::TestTask.new(:test) do |t|
|
5
10
|
t.libs << "test"
|
6
11
|
t.libs << "lib"
|
7
12
|
t.test_files = FileList["test/**/*_test.rb"]
|
8
13
|
end
|
9
14
|
|
15
|
+
namespace :test do
|
16
|
+
desc "Run integration tests using localstack"
|
17
|
+
Rake::TestTask.new(:integration) do |t|
|
18
|
+
t.libs << "integration"
|
19
|
+
t.test_files = FileList["integration/**/*_test.rb"]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
10
23
|
task default: :test
|
data/lib/webpacker_uploader.rb
CHANGED
@@ -1,23 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/core_ext/module"
|
3
4
|
require "active_support/core_ext/object/blank"
|
4
|
-
require "active_support/logger"
|
5
|
-
require "active_support/tagged_logging"
|
6
5
|
|
7
6
|
module WebpackerUploader
|
8
7
|
extend self
|
9
8
|
|
9
|
+
# @private
|
10
10
|
def instance=(instance)
|
11
11
|
@instance = instance
|
12
12
|
end
|
13
13
|
|
14
|
+
# @private
|
14
15
|
def instance
|
15
16
|
@instance ||= WebpackerUploader::Instance.new
|
16
17
|
end
|
17
18
|
|
18
|
-
|
19
|
+
# @!attribute [rw] config
|
20
|
+
# @see Instance#config
|
21
|
+
# @!scope class
|
22
|
+
# @!method configure
|
23
|
+
# @see Instance#configure
|
24
|
+
# @!scope class
|
25
|
+
# @!method upload!
|
26
|
+
# @return [void]
|
27
|
+
# @see Instance#upload!
|
28
|
+
# @!scope class
|
29
|
+
delegate :configure, :config, :upload!, to: :instance
|
19
30
|
end
|
20
31
|
|
32
|
+
require "webpacker_uploader/configuration"
|
21
33
|
require "webpacker_uploader/instance"
|
22
34
|
require "webpacker_uploader/manifest"
|
23
35
|
require "webpacker_uploader/mime"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/logger"
|
4
|
+
require "active_support/tagged_logging"
|
5
|
+
|
6
|
+
# This is the class which holds the configuration options.
|
7
|
+
#
|
8
|
+
# Options are set and retrieved using `WebpackerUploader.config`
|
9
|
+
# and `WebpackerUploader.configure`.
|
10
|
+
class WebpackerUploader::Configuration
|
11
|
+
# @return [Array] the file extentions ignored by the uploader.
|
12
|
+
attr_accessor :ignored_extensions
|
13
|
+
|
14
|
+
# @return [ActiveSupport::Logger] the logger to use.
|
15
|
+
attr_accessor :logger
|
16
|
+
|
17
|
+
# @return [Boolean] whether or not to log operations.
|
18
|
+
attr_accessor :log_output
|
19
|
+
|
20
|
+
# @return [Pathname] the path to manifest.json, defaults to Webpacker public manifest path.
|
21
|
+
attr_reader :public_manifest_path
|
22
|
+
|
23
|
+
# @return [Pathname] the public root path, defaults to Webpacker public root path.
|
24
|
+
attr_reader :public_path
|
25
|
+
|
26
|
+
alias_method :log_output?, :log_output
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
@ignored_extensions = []
|
30
|
+
@logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT))
|
31
|
+
@log_output = true
|
32
|
+
@public_manifest_path = ::Webpacker.config.public_manifest_path
|
33
|
+
@public_path = ::Webpacker.config.public_path
|
34
|
+
end
|
35
|
+
|
36
|
+
def public_manifest_path=(path)
|
37
|
+
@public_manifest_path = Pathname.new(path)
|
38
|
+
end
|
39
|
+
|
40
|
+
def public_path=(path)
|
41
|
+
@public_path = Pathname.new(path)
|
42
|
+
end
|
43
|
+
end
|
@@ -1,18 +1,51 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class WebpackerUploader::Instance
|
4
|
-
|
5
|
-
cattr_accessor(:ignored_extensions) { %w[.map] }
|
4
|
+
attr_writer :config
|
6
5
|
|
6
|
+
# @private
|
7
7
|
def manifest
|
8
8
|
@manifest ||= WebpackerUploader::Manifest.new
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
# Returns the global WebpackerUploader::Configuration object.
|
12
|
+
# Use this to set and retrieve specific configuration options.
|
13
|
+
#
|
14
|
+
# @return [WebpackerUploader::Configuration]
|
15
|
+
#
|
16
|
+
# @example Disable log output
|
17
|
+
# WebpackerUploader.config.log_output = false
|
18
|
+
#
|
19
|
+
# @example Get the list of excluded file extension
|
20
|
+
# puts WebpackerUploader.config.ignored_extensions
|
21
|
+
#
|
22
|
+
# @see WebpackerUploader::Configuration
|
23
|
+
def config
|
24
|
+
@config ||= WebpackerUploader::Configuration.new
|
25
|
+
end
|
15
26
|
|
27
|
+
# Yields the global configuration to a block. Use this to
|
28
|
+
# configure the base gem features.
|
29
|
+
#
|
30
|
+
# @example
|
31
|
+
# WebpackerUploader.configure do |config|
|
32
|
+
# config.ignored_extensions = [".png", ".jpg", ".webp"]
|
33
|
+
# config.log_output = false
|
34
|
+
# config.public_manifest_path = "path/to/manifest.json"
|
35
|
+
# config.public_path = "path/to/public/dir"
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# @see WebpackerUploader::Configuration
|
39
|
+
def configure
|
40
|
+
yield config
|
41
|
+
end
|
42
|
+
|
43
|
+
# Uploads assets using the supplied provider. Currently only AWS S3 is implemented.
|
44
|
+
#
|
45
|
+
# @return [void]
|
46
|
+
# @param provider [WebpackerUploader::Providers::Aws] A provider to use for file uploading.
|
47
|
+
# @param prefix [String] Used to prefix the remote file paths.
|
48
|
+
def upload!(provider, prefix: nil)
|
16
49
|
manifest.assets.each do |name, js_path|
|
17
50
|
path = js_path[1..-1]
|
18
51
|
|
@@ -23,13 +56,14 @@ class WebpackerUploader::Instance
|
|
23
56
|
"#{prefix}/#{path}"
|
24
57
|
end
|
25
58
|
|
26
|
-
file_path =
|
59
|
+
file_path = config.public_path.join(path)
|
27
60
|
|
28
|
-
if name.end_with?(*ignored_extensions)
|
29
|
-
logger.info("Skipping #{file_path}")
|
61
|
+
if name.end_with?(*config.ignored_extensions)
|
62
|
+
config.logger.info("Skipping #{file_path}") if config.log_output?
|
30
63
|
else
|
31
64
|
content_type = WebpackerUploader::Mime.mime_type(path)
|
32
|
-
|
65
|
+
|
66
|
+
config.logger.info("Processing #{file_path} as #{content_type}") if config.log_output?
|
33
67
|
|
34
68
|
provider.upload!(remote_path, file_path, content_type)
|
35
69
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class WebpackerUploader::Manifest
|
3
|
+
class WebpackerUploader::Manifest # @private
|
4
4
|
attr_reader :assets
|
5
5
|
|
6
6
|
def initialize
|
@@ -10,8 +10,8 @@ class WebpackerUploader::Manifest
|
|
10
10
|
private
|
11
11
|
|
12
12
|
def load
|
13
|
-
if
|
14
|
-
JSON.parse(
|
13
|
+
if WebpackerUploader.config.public_manifest_path.exist?
|
14
|
+
JSON.parse(WebpackerUploader.config.public_manifest_path.read).except("entrypoints")
|
15
15
|
else
|
16
16
|
{}
|
17
17
|
end
|
@@ -3,6 +3,12 @@
|
|
3
3
|
require "mime-types"
|
4
4
|
|
5
5
|
module WebpackerUploader::Mime
|
6
|
+
# Returns the mime type for the given file in the filesystem.
|
7
|
+
# If it's unable to detect the mime type, it returns +application/octet-stream+
|
8
|
+
# as a fallback.
|
9
|
+
#
|
10
|
+
# @param file_path [String] A file path in the local filesystem.
|
11
|
+
# @return [String] The file mime type.
|
6
12
|
def mime_type(file_path)
|
7
13
|
fallback = MIME::Types.type_for(file_path).first&.content_type || "application/octet-stream"
|
8
14
|
Rack::Mime.mime_type(File.extname(file_path), fallback)
|
@@ -3,34 +3,105 @@
|
|
3
3
|
require "aws-sdk-s3"
|
4
4
|
|
5
5
|
module WebpackerUploader
|
6
|
+
# Namespace for the upload providers.
|
6
7
|
module Providers
|
8
|
+
# AWS provider uploads files to AWS S3. It uses the +aws-sdk-s3+ gem.
|
7
9
|
class Aws
|
8
|
-
|
9
|
-
|
10
|
-
attr_reader :bucket
|
10
|
+
# AWS provider error class. Raised when the provided AWS client credentials options are wrong.
|
11
|
+
class CredentialsError < StandardError; end
|
11
12
|
|
13
|
+
# @param [Hash] options
|
14
|
+
# * :region (String) The S3 region name.
|
15
|
+
# * :bucket (String) The S3 bucket name.
|
16
|
+
# * :credentials (Hash) credential options for the AWS provider:
|
17
|
+
# * :profile_name (String) use a named profile configured in ~/.aws/credentials
|
18
|
+
# * :instance_profile (Boolean) use an instance profile from an EC2
|
19
|
+
# * :access_key_id (String) the AWS credentials access id.
|
20
|
+
# * :secret_access_key (String) the AWS credentials secret access key.
|
21
|
+
#
|
22
|
+
# @note Any unknown options will be passed directly to the +Aws::S3::Client+ class
|
23
|
+
# during initialization.
|
24
|
+
#
|
25
|
+
# @raise [CredentialsError] if the credential options Hash is not correct.
|
26
|
+
#
|
27
|
+
# @example Initialize using a named profile:
|
28
|
+
#
|
29
|
+
# provider_options = {
|
30
|
+
# credentials: { profile_name: "staging" },
|
31
|
+
# region: "eu-central-1",
|
32
|
+
# bucket: "application-assets-20200929124523451600000001"
|
33
|
+
# }
|
34
|
+
# provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
35
|
+
#
|
36
|
+
# @example Initialize using IAM keys
|
37
|
+
#
|
38
|
+
# provider_options = {
|
39
|
+
# credentials: { access_key_id: "KEY_ID", secret_access_key: "ACCESS_KEY" },
|
40
|
+
# region: "eu-central-1",
|
41
|
+
# bucket: "application-assets-20200929124523451600000001"
|
42
|
+
# }
|
43
|
+
# provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
44
|
+
#
|
45
|
+
# @example Initialize using an EC2 instance profile
|
46
|
+
#
|
47
|
+
# provider_options = {
|
48
|
+
# credentials: { instance_profile: true },
|
49
|
+
# region: "eu-central-1",
|
50
|
+
# bucket: "application-assets-20200929124523451600000001"
|
51
|
+
# }
|
52
|
+
# provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
12
53
|
def initialize(options)
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
54
|
+
@region = options.delete(:region)
|
55
|
+
@bucket_name = options.delete(:bucket)
|
56
|
+
@credentials = credentials(options.delete(:credentials))
|
57
|
+
@aws_options = options
|
58
|
+
@resource = ::Aws::S3::Resource.new(client: client)
|
16
59
|
end
|
17
60
|
|
61
|
+
# Uploads a file to AWS S3.
|
62
|
+
#
|
63
|
+
# @param object_key [String] Is the remote path name for the S3 object.
|
64
|
+
# @param file [Pathname] Path of the local file.
|
65
|
+
# @param content_type [String] The content type that will be set to the S3 object.
|
66
|
+
# @return [void]
|
18
67
|
def upload!(object_key, file, content_type = "")
|
19
|
-
object = @bucket.object(object_key)
|
68
|
+
object = @resource.bucket(@bucket_name).object(object_key)
|
20
69
|
object.upload_file(file, content_type: content_type)
|
21
70
|
end
|
22
71
|
|
23
72
|
private
|
24
|
-
|
25
73
|
def credentials(options)
|
26
|
-
if options
|
27
|
-
|
74
|
+
if options.key?(:profile_name)
|
75
|
+
{ profile: options[:profile_name] }
|
28
76
|
elsif options.key?(:instance_profile) && options[:instance_profile]
|
29
77
|
::Aws::InstanceProfileCredentials.new
|
30
|
-
|
78
|
+
elsif options.key?(:access_key_id) && options.key?(:secret_access_key)
|
31
79
|
::Aws::Credentials.new(options[:access_key_id], options[:secret_access_key])
|
80
|
+
else
|
81
|
+
raise CredentialsError, "Wrong AWS provider credentials options."
|
32
82
|
end
|
33
83
|
end
|
84
|
+
|
85
|
+
def profile?
|
86
|
+
@credentials.is_a?(Hash) && @credentials.key?(:profile)
|
87
|
+
end
|
88
|
+
|
89
|
+
def credentials_object?
|
90
|
+
!profile?
|
91
|
+
end
|
92
|
+
|
93
|
+
def client
|
94
|
+
::Aws::S3::Client.new(client_options)
|
95
|
+
end
|
96
|
+
|
97
|
+
def client_options
|
98
|
+
opts = {}
|
99
|
+
opts.merge!(@aws_options)
|
100
|
+
opts[:region] = @region
|
101
|
+
opts.merge!(@credentials) if profile?
|
102
|
+
opts[:credentials] = @credentials if credentials_object?
|
103
|
+
opts
|
104
|
+
end
|
34
105
|
end
|
35
106
|
end
|
36
107
|
end
|