webpacker_uploader 0.1.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +198 -14
- data/.yardopts +8 -0
- data/CHANGELOG.md +43 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +57 -46
- data/README.md +107 -12
- data/Rakefile +5 -0
- data/lib/webpacker_uploader.rb +15 -3
- data/lib/webpacker_uploader/configuration.rb +43 -0
- data/lib/webpacker_uploader/instance.rb +58 -22
- data/lib/webpacker_uploader/manifest.rb +3 -3
- data/lib/webpacker_uploader/mime.rb +17 -0
- data/lib/webpacker_uploader/providers/aws.rb +54 -7
- 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/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 +58 -14
- data/.github/workflows/rubocop.yml +0 -35
- data/.github/workflows/ruby.yml +0 -38
- data/bin/console +0 -14
- data/bin/setup +0 -8
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# WebpackerUploader
|
2
2
|
|
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
|
+
[![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
|
+
[![Gem](https://img.shields.io/gem/v/webpacker_uploader?style=flat-square)](https://rubygems.org/gems/webpacker_uploader)
|
6
|
+
|
3
7
|
Webpacker uploader provides an easy way to upload your assets to AWS S3.
|
4
8
|
It knows which files to upload by reading the `manifest.json` file.
|
5
9
|
|
@@ -11,14 +15,14 @@ S3 + CDN, outside of your Rails application.
|
|
11
15
|
Add this line to your application's Gemfile:
|
12
16
|
|
13
17
|
```ruby
|
14
|
-
gem "webpacker_uploader"
|
18
|
+
gem "webpacker_uploader", require: false
|
15
19
|
```
|
16
20
|
|
17
|
-
|
21
|
+
and run:
|
18
22
|
|
19
23
|
$ bundle install
|
20
24
|
|
21
|
-
|
25
|
+
or:
|
22
26
|
|
23
27
|
$ gem install webpacker_uploader
|
24
28
|
|
@@ -30,18 +34,109 @@ gem "aws-sdk-s3", require: false
|
|
30
34
|
|
31
35
|
## Usage
|
32
36
|
|
37
|
+
Usually, in a Rake task you would add the following to upload the assets to AWS S3:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require "webpacker_uploader"
|
41
|
+
require "webpacker_uploader/providers/aws"
|
42
|
+
|
43
|
+
provider_options = {
|
44
|
+
credentials: { profile_name: "staging" },
|
45
|
+
region: "eu-central-1",
|
46
|
+
bucket: "application-assets-20200929124523451600000001"
|
47
|
+
}
|
48
|
+
|
49
|
+
provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
50
|
+
WebpackerUploader.upload!(provider)
|
51
|
+
```
|
52
|
+
|
53
|
+
Currently, the only provider implemented is the AWS S3 provider.
|
54
|
+
|
55
|
+
### AWS S3 provider
|
56
|
+
|
57
|
+
The AWS S3 provider credentials can be configured in three ways.
|
58
|
+
Passing a named profile name:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
provider_options = {
|
62
|
+
credentials: { profile_name: "staging" },
|
63
|
+
region: "eu-central-1",
|
64
|
+
bucket: "application-assets-20200929124523451600000001"
|
65
|
+
}
|
66
|
+
|
67
|
+
provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
68
|
+
```
|
69
|
+
|
70
|
+
passing the access and the secret keys directly:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
provider_options = {
|
74
|
+
credentials: { access_key_id: "AKIAIOSFODNN7EXAMPLE", secret_access_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY" },
|
75
|
+
region: "eu-central-1",
|
76
|
+
bucket: "application-assets-20200929124523451600000001"
|
77
|
+
}
|
78
|
+
|
79
|
+
provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
80
|
+
```
|
81
|
+
|
82
|
+
or using an EC2 instance profile:
|
83
|
+
|
33
84
|
```ruby
|
34
|
-
|
35
|
-
|
85
|
+
provider_options = {
|
86
|
+
credentials: { instance_profile: true },
|
87
|
+
region: "eu-central-1",
|
88
|
+
bucket: "application-assets-20200929124523451600000001"
|
89
|
+
}
|
36
90
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
91
|
+
provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
92
|
+
```
|
93
|
+
|
94
|
+
### Prefix remote files
|
95
|
+
|
96
|
+
Uploaded files can be prefixed by setting the `prefix` parameter during upload:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
WebpackerUploader.upload!(provider, prefix: "assets")
|
100
|
+
```
|
101
|
+
|
102
|
+
This will prefix all remote file paths with `assets` so instead of storing `packs/application-dd6b1cd38bfa093df600.css` it
|
103
|
+
will store `assets/packs/application-dd6b1cd38bfa093df600.css`.
|
104
|
+
|
105
|
+
### Configuration
|
106
|
+
|
107
|
+
WebpackerUploader currently supports the following configuration options:
|
108
|
+
|
109
|
+
| option | description | default value |
|
110
|
+
|----------------------|--------------------------------------------------------------|---------------------------------------|
|
111
|
+
| ignored_extensions | Which files uploader should skip based on the file extension | [] |
|
112
|
+
| logger | The logger to use for logging | ActiveSupport::Logger.new(STDOUT) |
|
113
|
+
| log_output | Log output as we upload files | true |
|
114
|
+
| public_manifest_path | The webpack manifest path | Webpacker.config.public_manifest_path |
|
115
|
+
| public_path | The webpack public output path | Webpacker.config.public_path |
|
42
116
|
|
43
|
-
|
44
|
-
|
117
|
+
It can be configured using a block:
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
WebpackerUploader.configure do |config|
|
121
|
+
config.ignored_extensions = [".png", ".jpg", ".webp"]
|
122
|
+
config.log_output = false
|
123
|
+
config.public_manifest_path = "path/to/manifest.json"
|
124
|
+
config.public_path = "path/to/public/dir"
|
125
|
+
end
|
126
|
+
```
|
127
|
+
|
128
|
+
or directly:
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
WebpackerUploader.config.log_output = false
|
132
|
+
```
|
133
|
+
|
134
|
+
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))
|
135
|
+
and everything is uploaded by default now. To retain the previous functionality use:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# skip uploading map files
|
139
|
+
WebpackerUploader.config.ignored_extensions = [".map"]
|
45
140
|
```
|
46
141
|
|
47
142
|
## Development
|
data/Rakefile
CHANGED
data/lib/webpacker_uploader.rb
CHANGED
@@ -1,23 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/object/blank"
|
4
|
-
require "active_support/logger"
|
5
|
-
require "active_support/tagged_logging"
|
6
4
|
|
7
5
|
module WebpackerUploader
|
8
6
|
extend self
|
9
7
|
|
8
|
+
# @private
|
10
9
|
def instance=(instance)
|
11
10
|
@instance = instance
|
12
11
|
end
|
13
12
|
|
13
|
+
# @private
|
14
14
|
def instance
|
15
15
|
@instance ||= WebpackerUploader::Instance.new
|
16
16
|
end
|
17
17
|
|
18
|
-
|
18
|
+
# @!attribute [rw] config
|
19
|
+
# @see Instance#config
|
20
|
+
# @!scope class
|
21
|
+
# @!method configure
|
22
|
+
# @see Instance#configure
|
23
|
+
# @!scope class
|
24
|
+
# @!method upload!
|
25
|
+
# @return [void]
|
26
|
+
# @see Instance#upload!
|
27
|
+
# @!scope class
|
28
|
+
delegate :configure, :config, :upload!, to: :instance
|
19
29
|
end
|
20
30
|
|
31
|
+
require "webpacker_uploader/configuration"
|
21
32
|
require "webpacker_uploader/instance"
|
22
33
|
require "webpacker_uploader/manifest"
|
34
|
+
require "webpacker_uploader/mime"
|
23
35
|
require "webpacker_uploader/version"
|
@@ -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,36 +1,72 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "mime-types"
|
4
|
-
|
5
3
|
class WebpackerUploader::Instance
|
6
|
-
|
7
|
-
IGNORE_EXTENSION = %w[.map].freeze
|
8
|
-
public_constant :IGNORE_EXTENSION
|
9
|
-
|
10
|
-
cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
|
4
|
+
attr_writer :config
|
11
5
|
|
6
|
+
# @private
|
12
7
|
def manifest
|
13
8
|
@manifest ||= WebpackerUploader::Manifest.new
|
14
9
|
end
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
20
26
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
28
41
|
end
|
29
42
|
|
30
|
-
|
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)
|
49
|
+
manifest.assets.each do |name, js_path|
|
50
|
+
path = js_path[1..-1]
|
51
|
+
|
52
|
+
remote_path =
|
53
|
+
if prefix.nil?
|
54
|
+
path
|
55
|
+
else
|
56
|
+
"#{prefix}/#{path}"
|
57
|
+
end
|
31
58
|
|
32
|
-
|
33
|
-
|
34
|
-
|
59
|
+
file_path = config.public_path.join(path)
|
60
|
+
|
61
|
+
if name.end_with?(*config.ignored_extensions)
|
62
|
+
config.logger.info("Skipping #{file_path}") if config.log_output?
|
63
|
+
else
|
64
|
+
content_type = WebpackerUploader::Mime.mime_type(path)
|
65
|
+
|
66
|
+
config.logger.info("Processing #{file_path} as #{content_type}") if config.log_output?
|
67
|
+
|
68
|
+
provider.upload!(remote_path, file_path, content_type)
|
69
|
+
end
|
35
70
|
end
|
71
|
+
end
|
36
72
|
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
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "mime-types"
|
4
|
+
|
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.
|
12
|
+
def mime_type(file_path)
|
13
|
+
fallback = MIME::Types.type_for(file_path).first&.content_type || "application/octet-stream"
|
14
|
+
Rack::Mime.mime_type(File.extname(file_path), fallback)
|
15
|
+
end
|
16
|
+
module_function :mime_type
|
17
|
+
end
|
@@ -3,29 +3,76 @@
|
|
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
|
-
attr_reader :client
|
9
|
-
attr_reader :resource
|
10
|
-
attr_reader :bucket
|
10
|
+
attr_reader :client, :resource, :bucket # @private
|
11
11
|
|
12
|
+
# @param [Hash] options
|
13
|
+
# * :region (String) The S3 region name.
|
14
|
+
# * :bucket (String) The S3 bucket name.
|
15
|
+
# * :credentials (Hash) credential options for the AWS provider:
|
16
|
+
# * :profile_name (String) use a named profile configured in ~/.aws/credentials
|
17
|
+
# * :instance_profile (Boolean) use an instance profile from an EC2
|
18
|
+
# * :access_key_id (String) the AWS credentials access id.
|
19
|
+
# * :secret_access_key (String) the AWS credentials secret access key.
|
20
|
+
#
|
21
|
+
# @example Initialize the using a named profile:
|
22
|
+
#
|
23
|
+
# provider_options = {
|
24
|
+
# credentials: { profile_name: "staging" },
|
25
|
+
# region: "eu-central-1",
|
26
|
+
# bucket: "application-assets-20200929124523451600000001"
|
27
|
+
# }
|
28
|
+
# provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
29
|
+
#
|
30
|
+
# @example Initialize using IAM keys
|
31
|
+
#
|
32
|
+
# provider_options = {
|
33
|
+
# credentials: { access_key_id: "KEY_ID", secret_access_key: "ACCESS_KEY" },
|
34
|
+
# region: "eu-central-1",
|
35
|
+
# bucket: "application-assets-20200929124523451600000001"
|
36
|
+
# }
|
37
|
+
# provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
38
|
+
#
|
39
|
+
# @example Initialize using an EC2 instance profile
|
40
|
+
#
|
41
|
+
# provider_options = {
|
42
|
+
# credentials: { instance_profile: true },
|
43
|
+
# region: "eu-central-1",
|
44
|
+
# bucket: "application-assets-20200929124523451600000001"
|
45
|
+
# }
|
46
|
+
# provider = WebpackerUploader::Providers::Aws.new(provider_options)
|
12
47
|
def initialize(options)
|
13
|
-
@client = ::Aws::S3::Client.new(
|
48
|
+
@client = ::Aws::S3::Client.new(client_options(options))
|
14
49
|
@resource = ::Aws::S3::Resource.new(client: @client)
|
15
50
|
@bucket = @resource.bucket(options[:bucket])
|
16
51
|
end
|
17
52
|
|
53
|
+
# Uploads a file to AWS S3.
|
54
|
+
#
|
55
|
+
# @param object_key [String] Is the remote path name for the S3 object.
|
56
|
+
# @param file [Pathname] Path of the local file.
|
57
|
+
# @param content_type [String] The content type that will be set to the S3 object.
|
58
|
+
# @return [void]
|
18
59
|
def upload!(object_key, file, content_type = "")
|
19
60
|
object = @bucket.object(object_key)
|
20
61
|
object.upload_file(file, content_type: content_type)
|
21
62
|
end
|
22
63
|
|
23
64
|
private
|
65
|
+
def client_options(options)
|
66
|
+
opts = { region: options[:region] }
|
67
|
+
opts[:profile] = options[:profile_name] if options.key? :profile_name
|
68
|
+
opts[:credentials] = credentials(options) if credentials(options)
|
69
|
+
opts
|
70
|
+
end
|
24
71
|
|
25
72
|
def credentials(options)
|
26
|
-
if options[:
|
27
|
-
::Aws::
|
28
|
-
|
73
|
+
if options.key?(:instance_profile) && options[:instance_profile]
|
74
|
+
::Aws::InstanceProfileCredentials.new
|
75
|
+
elsif options.key?(:access_key_id)
|
29
76
|
::Aws::Credentials.new(options[:access_key_id], options[:secret_access_key])
|
30
77
|
end
|
31
78
|
end
|