aboutsummaryrefslogtreecommitdiffstats
path: root/activestorage
diff options
context:
space:
mode:
Diffstat (limited to 'activestorage')
-rw-r--r--activestorage/.babelrc3
-rw-r--r--activestorage/CHANGELOG.md113
-rw-r--r--activestorage/README.md4
-rw-r--r--activestorage/Rakefile12
-rw-r--r--activestorage/app/assets/javascripts/activestorage.js940
-rw-r--r--activestorage/app/controllers/active_storage/base_controller.rb8
-rw-r--r--activestorage/app/controllers/active_storage/blobs_controller.rb2
-rw-r--r--activestorage/app/controllers/active_storage/disk_controller.rb19
-rw-r--r--activestorage/app/controllers/active_storage/representations_controller.rb2
-rw-r--r--activestorage/app/controllers/concerns/active_storage/set_current.rb15
-rw-r--r--activestorage/app/javascript/activestorage/file_checksum.js2
-rw-r--r--activestorage/app/javascript/activestorage/ujs.js13
-rw-r--r--activestorage/app/jobs/active_storage/analyze_job.rb2
-rw-r--r--activestorage/app/jobs/active_storage/purge_job.rb4
-rw-r--r--activestorage/app/models/active_storage/attachment.rb22
-rw-r--r--activestorage/app/models/active_storage/blob.rb72
-rw-r--r--activestorage/app/models/active_storage/blob/representable.rb10
-rw-r--r--activestorage/app/models/active_storage/preview.rb6
-rw-r--r--activestorage/app/models/active_storage/variant.rb103
-rw-r--r--activestorage/app/models/active_storage/variation.rb55
-rw-r--r--activestorage/db/migrate/20170806125915_create_active_storage_tables.rb1
-rw-r--r--activestorage/lib/active_storage.rb10
-rw-r--r--activestorage/lib/active_storage/analyzer.rb13
-rw-r--r--activestorage/lib/active_storage/analyzer/video_analyzer.rb4
-rw-r--r--activestorage/lib/active_storage/attached.rb29
-rw-r--r--activestorage/lib/active_storage/attached/changes.rb16
-rw-r--r--activestorage/lib/active_storage/attached/changes/create_many.rb46
-rw-r--r--activestorage/lib/active_storage/attached/changes/create_one.rb68
-rw-r--r--activestorage/lib/active_storage/attached/changes/create_one_of_many.rb10
-rw-r--r--activestorage/lib/active_storage/attached/changes/delete_many.rb23
-rw-r--r--activestorage/lib/active_storage/attached/changes/delete_one.rb19
-rw-r--r--activestorage/lib/active_storage/attached/macros.rb110
-rw-r--r--activestorage/lib/active_storage/attached/many.rb26
-rw-r--r--activestorage/lib/active_storage/attached/model.rb140
-rw-r--r--activestorage/lib/active_storage/attached/one.rb35
-rw-r--r--activestorage/lib/active_storage/downloader.rb44
-rw-r--r--activestorage/lib/active_storage/downloading.rb9
-rw-r--r--activestorage/lib/active_storage/engine.rb23
-rw-r--r--activestorage/lib/active_storage/errors.rb25
-rw-r--r--activestorage/lib/active_storage/log_subscriber.rb2
-rw-r--r--activestorage/lib/active_storage/previewer.rb36
-rw-r--r--activestorage/lib/active_storage/previewer/poppler_pdf_previewer.rb2
-rw-r--r--activestorage/lib/active_storage/previewer/video_previewer.rb5
-rw-r--r--activestorage/lib/active_storage/reflection.rb64
-rw-r--r--activestorage/lib/active_storage/service.rb6
-rw-r--r--activestorage/lib/active_storage/service/azure_storage_service.rb36
-rw-r--r--activestorage/lib/active_storage/service/configurator.rb4
-rw-r--r--activestorage/lib/active_storage/service/disk_service.rb38
-rw-r--r--activestorage/lib/active_storage/service/gcs_service.rb64
-rw-r--r--activestorage/lib/active_storage/service/s3_service.rb14
-rw-r--r--activestorage/lib/active_storage/transformers/image_processing_transformer.rb39
-rw-r--r--activestorage/lib/active_storage/transformers/mini_magick_transformer.rb38
-rw-r--r--activestorage/lib/active_storage/transformers/transformer.rb42
-rw-r--r--activestorage/lib/tasks/activestorage.rake3
-rw-r--r--activestorage/package.json10
-rw-r--r--activestorage/rollup.config.js28
-rw-r--r--activestorage/test/analyzer/image_analyzer_test.rb5
-rw-r--r--activestorage/test/analyzer/video_analyzer_test.rb5
-rw-r--r--activestorage/test/controllers/disk_controller_test.rb37
-rw-r--r--activestorage/test/dummy/config/secrets.yml2
-rw-r--r--activestorage/test/jobs/purge_job_test.rb27
-rw-r--r--activestorage/test/models/attached/many_test.rb596
-rw-r--r--activestorage/test/models/attached/one_test.rb513
-rw-r--r--activestorage/test/models/attachments_test.rb459
-rw-r--r--activestorage/test/models/blob_test.rb86
-rw-r--r--activestorage/test/models/presence_validation_test.rb30
-rw-r--r--activestorage/test/models/preview_test.rb4
-rw-r--r--activestorage/test/models/reflection_test.rb34
-rw-r--r--activestorage/test/models/variant_test.rb106
-rw-r--r--activestorage/test/previewer/video_previewer_test.rb5
-rw-r--r--activestorage/test/service/azure_storage_service_test.rb19
-rw-r--r--activestorage/test/service/configurator_test.rb6
-rw-r--r--activestorage/test/service/disk_service_test.rb6
-rw-r--r--activestorage/test/service/gcs_service_test.rb4
-rw-r--r--activestorage/test/service/mirror_service_test.rb10
-rw-r--r--activestorage/test/service/s3_service_test.rb2
-rw-r--r--activestorage/test/service/shared_service_tests.rb57
-rw-r--r--activestorage/test/test_helper.rb20
-rw-r--r--activestorage/webpack.config.js26
-rw-r--r--activestorage/yarn.lock1481
80 files changed, 3726 insertions, 2303 deletions
diff --git a/activestorage/.babelrc b/activestorage/.babelrc
index a8211d329f..ed751f8745 100644
--- a/activestorage/.babelrc
+++ b/activestorage/.babelrc
@@ -1,5 +1,8 @@
{
"presets": [
["env", { "modules": false } ]
+ ],
+ "plugins": [
+ "external-helpers"
]
}
diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md
index d794afb0e6..92e300a440 100644
--- a/activestorage/CHANGELOG.md
+++ b/activestorage/CHANGELOG.md
@@ -1,3 +1,116 @@
+* `ActiveStorage::Service::AzureStorageService` only handles specifically
+ relevant types of `Azure::Core::Http::HTTPError`. It previously obscured
+ other types of `HTTPError`, which is the azure-storage gem’s catch-all
+ exception class.
+
+ *Cameron Bothner*
+
+* `ActiveStorage::DiskController#show` generates a 404 Not Found response when
+ the requested file is missing from the disk service. It previously raised
+ `Errno::ENOENT`.
+
+ *Cameron Bothner*
+
+* `ActiveStorage::Blob#download` and `ActiveStorage::Blob#open` raise
+ `ActiveStorage::FileNotFoundError` when the corresponding file is missing
+ from the storage service. Services translate service-specific missing object
+ exceptions (e.g. `Google::Cloud::NotFoundError` for the GCS service and
+ `Errno::ENOENT` for the disk service) into
+ `ActiveStorage::FileNotFoundError`.
+
+ *Cameron Bothner*
+
+* Added the `ActiveStorage::SetCurrent` concern for custom Active Storage
+ controllers that can't inherit from `ActiveStorage::BaseController`.
+
+ *George Claghorn*
+
+* Active Storage error classes like `ActiveStorage::IntegrityError` and
+ `ActiveStorage::UnrepresentableError` now inherit from `ActiveStorage::Error`
+ instead of `StandardError`. This permits rescuing `ActiveStorage::Error` to
+ handle all Active Storage errors.
+
+ *Andrei Makarov*, *George Claghorn*
+
+* Uploaded files assigned to a record are persisted to storage when the record
+ is saved instead of immediately.
+
+ In Rails 5.2, the following causes an uploaded file in `params[:avatar]` to
+ be stored:
+
+ ```ruby
+ @user.avatar = params[:avatar]
+ ```
+
+ In Rails 6, the uploaded file is stored when `@user` is successfully saved.
+
+ *George Claghorn*
+
+* Add the ability to reflect on defined attachments using the existing
+ ActiveRecord reflection mechanism.
+
+ *Kevin Deisz*
+
+* Variant arguments of `false` or `nil` will no longer be passed to the
+ processor. For example, the following will not have the monochrome
+ variation applied:
+
+ ```ruby
+ avatar.variant(monochrome: false)
+ ```
+
+ *Jacob Smith*
+
+* Generated attachment getter and setter methods are created
+ within the model's `GeneratedAssociationMethods` module to
+ allow overriding and composition using `super`.
+
+ *Josh Susser*, *Jamon Douglas*
+
+* Add `ActiveStorage::Blob#open`, which downloads a blob to a tempfile on disk
+ and yields the tempfile. Deprecate `ActiveStorage::Downloading`.
+
+ *David Robertson*, *George Claghorn*
+
+* Pass in `identify: false` as an argument when providing a `content_type` for
+ `ActiveStorage::Attached::{One,Many}#attach` to bypass automatic content
+ type inference. For example:
+
+ ```ruby
+ @message.image.attach(
+ io: File.open('/path/to/file'),
+ filename: 'file.pdf',
+ content_type: 'application/pdf',
+ identify: false
+ )
+ ```
+
+ *Ryan Davidson*
+
+* The Google Cloud Storage service properly supports streaming downloads.
+ It now requires version 1.11 or newer of the google-cloud-storage gem.
+
+ *George Claghorn*
+
+* Use the [ImageProcessing](https://github.com/janko-m/image_processing) gem
+ for Active Storage variants, and deprecate the MiniMagick backend.
+
+ This means that variants are now automatically oriented if the original
+ image was rotated. Also, in addition to the existing ImageMagick
+ operations, variants can now use `:resize_to_fit`, `:resize_to_fill`, and
+ other ImageProcessing macros. These are now recommended over raw `:resize`,
+ as they also sharpen the thumbnail after resizing.
+
+ The ImageProcessing gem also comes with a backend implemented on
+ [libvips](http://jcupitt.github.io/libvips/), an alternative to
+ ImageMagick which has significantly better performance than
+ ImageMagick in most cases, both in terms of speed and memory usage. In
+ Active Storage it's now possible to switch to the libvips backend by
+ changing `Rails.application.config.active_storage.variant_processor` to
+ `:vips`.
+
+ *Janko Marohnić*
+
* Rails 6 requires Ruby 2.4.1 or newer.
*Jeremy Daer*
diff --git a/activestorage/README.md b/activestorage/README.md
index 85ab70dac6..b677721d95 100644
--- a/activestorage/README.md
+++ b/activestorage/README.md
@@ -4,7 +4,7 @@ Active Storage makes it simple to upload and reference files in cloud services l
Files can be uploaded from the server to the cloud or directly from the client to the cloud.
-Image files can furthermore be transformed using on-demand variants for quality, aspect ratio, size, or any other [MiniMagick](https://github.com/minimagick/minimagick) supported transformation.
+Image files can furthermore be transformed using on-demand variants for quality, aspect ratio, size, or any other [MiniMagick](https://github.com/minimagick/minimagick) or [Vips](http://www.rubydoc.info/gems/ruby-vips/Vips/Image) supported transformation.
## Compared to other storage solutions
@@ -99,7 +99,7 @@ Variation of image attachment:
```erb
<%# Hitting the variant URL will lazy transform the original blob and then redirect to its new service location %>
-<%= image_tag user.avatar.variant(resize: "100x100") %>
+<%= image_tag user.avatar.variant(resize_to_fit: [100, 100]) %>
```
## Direct uploads
diff --git a/activestorage/Rakefile b/activestorage/Rakefile
index 7dc69e04ea..2e86d3d860 100644
--- a/activestorage/Rakefile
+++ b/activestorage/Rakefile
@@ -4,12 +4,12 @@ require "bundler/setup"
require "bundler/gem_tasks"
require "rake/testtask"
-Rake::TestTask.new do |test|
- test.libs << "app/controllers"
- test.libs << "test"
- test.test_files = FileList["test/**/*_test.rb"]
- test.verbose = true
- test.warning = false
+Rake::TestTask.new do |t|
+ t.libs << "app/controllers"
+ t.libs << "test"
+ t.test_files = FileList["test/**/*_test.rb"]
+ t.verbose = true
+ t.warning = true
end
task :package
diff --git a/activestorage/app/assets/javascripts/activestorage.js b/activestorage/app/assets/javascripts/activestorage.js
index 3e1fbc7b50..375eb6b533 100644
--- a/activestorage/app/assets/javascripts/activestorage.js
+++ b/activestorage/app/assets/javascripts/activestorage.js
@@ -1 +1,939 @@
-!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ActiveStorage=e():t.ActiveStorage=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,e),i.l=!0,i.exports}var r={};return e.m=t,e.c=r,e.d=function(t,r,n){e.o(t,r)||Object.defineProperty(t,r,{configurable:!1,enumerable:!0,get:n})},e.n=function(t){var r=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(r,"a",r),r},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=2)}([function(t,e,r){"use strict";function n(t){var e=a(document.head,'meta[name="'+t+'"]');if(e)return e.getAttribute("content")}function i(t,e){return"string"==typeof t&&(e=t,t=document),o(t.querySelectorAll(e))}function a(t,e){return"string"==typeof t&&(e=t,t=document),t.querySelector(e)}function u(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},n=t.disabled,i=r.bubbles,a=r.cancelable,u=r.detail,o=document.createEvent("Event");o.initEvent(e,i||!0,a||!0),o.detail=u||{};try{t.disabled=!1,t.dispatchEvent(o)}finally{t.disabled=n}return o}function o(t){return Array.isArray(t)?t:Array.from?Array.from(t):[].slice.call(t)}e.d=n,e.c=i,e.b=a,e.a=u,e.e=o},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function i(t,e){if(t&&"function"==typeof t[e]){for(var r=arguments.length,n=Array(r>2?r-2:0),i=2;i<r;i++)n[i-2]=arguments[i];return t[e].apply(t,n)}}r.d(e,"a",function(){return c});var a=r(6),u=r(8),o=r(9),s=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),f=0,c=function(){function t(e,r,i){n(this,t),this.id=++f,this.file=e,this.url=r,this.delegate=i}return s(t,[{key:"create",value:function(t){var e=this;a.a.create(this.file,function(r,n){if(r)return void t(r);var a=new u.a(e.file,n,e.url);i(e.delegate,"directUploadWillCreateBlobWithXHR",a.xhr),a.create(function(r){if(r)t(r);else{var n=new o.a(a);i(e.delegate,"directUploadWillStoreFileWithXHR",n.xhr),n.create(function(e){e?t(e):t(null,a.toJSON())})}})})}}]),t}()},function(t,e,r){"use strict";function n(){window.ActiveStorage&&Object(i.a)()}Object.defineProperty(e,"__esModule",{value:!0});var i=r(3),a=r(1);r.d(e,"start",function(){return i.a}),r.d(e,"DirectUpload",function(){return a.a}),setTimeout(n,1)},function(t,e,r){"use strict";function n(){d||(d=!0,document.addEventListener("submit",i),document.addEventListener("ajax:before",a))}function i(t){u(t)}function a(t){"FORM"==t.target.tagName&&u(t)}function u(t){var e=t.target;if(e.hasAttribute(l))return void t.preventDefault();var r=new c.a(e),n=r.inputs;n.length&&(t.preventDefault(),e.setAttribute(l,""),n.forEach(s),r.start(function(t){e.removeAttribute(l),t?n.forEach(f):o(e)}))}function o(t){var e=Object(h.b)(t,"input[type=submit]");if(e){var r=e,n=r.disabled;e.disabled=!1,e.focus(),e.click(),e.disabled=n}else e=document.createElement("input"),e.type="submit",e.style.display="none",t.appendChild(e),e.click(),t.removeChild(e)}function s(t){t.disabled=!0}function f(t){t.disabled=!1}e.a=n;var c=r(4),h=r(0),l="data-direct-uploads-processing",d=!1},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(5),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o="input[type=file][data-direct-upload-url]:not([disabled])",s=function(){function t(e){n(this,t),this.form=e,this.inputs=Object(a.c)(e,o).filter(function(t){return t.files.length})}return u(t,[{key:"start",value:function(t){var e=this,r=this.createDirectUploadControllers();this.dispatch("start"),function n(){var i=r.shift();i?i.start(function(r){r?(t(r),e.dispatch("end")):n()}):(t(),e.dispatch("end"))}()}},{key:"createDirectUploadControllers",value:function(){var t=[];return this.inputs.forEach(function(e){Object(a.e)(e.files).forEach(function(r){var n=new i.a(e,r);t.push(n)})}),t}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return Object(a.a)(this.form,"direct-uploads:"+t,{detail:e})}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return o});var i=r(1),a=r(0),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=function(){function t(e,r){n(this,t),this.input=e,this.file=r,this.directUpload=new i.a(this.file,this.url,this),this.dispatch("initialize")}return u(t,[{key:"start",value:function(t){var e=this,r=document.createElement("input");r.type="hidden",r.name=this.input.name,this.input.insertAdjacentElement("beforebegin",r),this.dispatch("start"),this.directUpload.create(function(n,i){n?(r.parentNode.removeChild(r),e.dispatchError(n)):r.value=i.signed_id,e.dispatch("end"),t(n)})}},{key:"uploadRequestDidProgress",value:function(t){var e=t.loaded/t.total*100;e&&this.dispatch("progress",{progress:e})}},{key:"dispatch",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return e.file=this.file,e.id=this.directUpload.id,Object(a.a)(this.input,"direct-upload:"+t,{detail:e})}},{key:"dispatchError",value:function(t){this.dispatch("error",{error:t}).defaultPrevented||alert(t)}},{key:"directUploadWillCreateBlobWithXHR",value:function(t){this.dispatch("before-blob-request",{xhr:t})}},{key:"directUploadWillStoreFileWithXHR",value:function(t){var e=this;this.dispatch("before-storage-request",{xhr:t}),t.upload.addEventListener("progress",function(t){return e.uploadRequestDidProgress(t)})}},{key:"url",get:function(){return this.input.getAttribute("data-direct-upload-url")}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return s});var i=r(7),a=r.n(i),u=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),o=File.prototype.slice||File.prototype.mozSlice||File.prototype.webkitSlice,s=function(){function t(e){n(this,t),this.file=e,this.chunkSize=2097152,this.chunkCount=Math.ceil(this.file.size/this.chunkSize),this.chunkIndex=0}return u(t,null,[{key:"create",value:function(e,r){new t(e).create(r)}}]),u(t,[{key:"create",value:function(t){var e=this;this.callback=t,this.md5Buffer=new a.a.ArrayBuffer,this.fileReader=new FileReader,this.fileReader.addEventListener("load",function(t){return e.fileReaderDidLoad(t)}),this.fileReader.addEventListener("error",function(t){return e.fileReaderDidError(t)}),this.readNextChunk()}},{key:"fileReaderDidLoad",value:function(t){if(this.md5Buffer.append(t.target.result),!this.readNextChunk()){var e=this.md5Buffer.end(!0),r=btoa(e);this.callback(null,r)}}},{key:"fileReaderDidError",value:function(t){this.callback("Error reading "+this.file.name)}},{key:"readNextChunk",value:function(){if(this.chunkIndex<this.chunkCount){var t=this.chunkIndex*this.chunkSize,e=Math.min(t+this.chunkSize,this.file.size),r=o.call(this.file,t,e);return this.fileReader.readAsArrayBuffer(r),this.chunkIndex++,!0}return!1}}]),t}()},function(t,e,r){!function(e){t.exports=e()}(function(t){"use strict";function e(t,e){var r=t[0],n=t[1],i=t[2],a=t[3];r+=(n&i|~n&a)+e[0]-680876936|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[1]-389564586|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[2]+606105819|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[3]-1044525330|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[4]-176418897|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[5]+1200080426|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[6]-1473231341|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[7]-45705983|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[8]+1770035416|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[9]-1958414417|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[10]-42063|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[11]-1990404162|0,n=(n<<22|n>>>10)+i|0,r+=(n&i|~n&a)+e[12]+1804603682|0,r=(r<<7|r>>>25)+n|0,a+=(r&n|~r&i)+e[13]-40341101|0,a=(a<<12|a>>>20)+r|0,i+=(a&r|~a&n)+e[14]-1502002290|0,i=(i<<17|i>>>15)+a|0,n+=(i&a|~i&r)+e[15]+1236535329|0,n=(n<<22|n>>>10)+i|0,r+=(n&a|i&~a)+e[1]-165796510|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[6]-1069501632|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[11]+643717713|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[0]-373897302|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[5]-701558691|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[10]+38016083|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[15]-660478335|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[4]-405537848|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[9]+568446438|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[14]-1019803690|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[3]-187363961|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[8]+1163531501|0,n=(n<<20|n>>>12)+i|0,r+=(n&a|i&~a)+e[13]-1444681467|0,r=(r<<5|r>>>27)+n|0,a+=(r&i|n&~i)+e[2]-51403784|0,a=(a<<9|a>>>23)+r|0,i+=(a&n|r&~n)+e[7]+1735328473|0,i=(i<<14|i>>>18)+a|0,n+=(i&r|a&~r)+e[12]-1926607734|0,n=(n<<20|n>>>12)+i|0,r+=(n^i^a)+e[5]-378558|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[8]-2022574463|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[11]+1839030562|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[14]-35309556|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[1]-1530992060|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[4]+1272893353|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[7]-155497632|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[10]-1094730640|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[13]+681279174|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[0]-358537222|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[3]-722521979|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[6]+76029189|0,n=(n<<23|n>>>9)+i|0,r+=(n^i^a)+e[9]-640364487|0,r=(r<<4|r>>>28)+n|0,a+=(r^n^i)+e[12]-421815835|0,a=(a<<11|a>>>21)+r|0,i+=(a^r^n)+e[15]+530742520|0,i=(i<<16|i>>>16)+a|0,n+=(i^a^r)+e[2]-995338651|0,n=(n<<23|n>>>9)+i|0,r+=(i^(n|~a))+e[0]-198630844|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[7]+1126891415|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[14]-1416354905|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[5]-57434055|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[12]+1700485571|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[3]-1894986606|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[10]-1051523|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[1]-2054922799|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[8]+1873313359|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[15]-30611744|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[6]-1560198380|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[13]+1309151649|0,n=(n<<21|n>>>11)+i|0,r+=(i^(n|~a))+e[4]-145523070|0,r=(r<<6|r>>>26)+n|0,a+=(n^(r|~i))+e[11]-1120210379|0,a=(a<<10|a>>>22)+r|0,i+=(r^(a|~n))+e[2]+718787259|0,i=(i<<15|i>>>17)+a|0,n+=(a^(i|~r))+e[9]-343485551|0,n=(n<<21|n>>>11)+i|0,t[0]=r+t[0]|0,t[1]=n+t[1]|0,t[2]=i+t[2]|0,t[3]=a+t[3]|0}function r(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t.charCodeAt(e)+(t.charCodeAt(e+1)<<8)+(t.charCodeAt(e+2)<<16)+(t.charCodeAt(e+3)<<24);return r}function n(t){var e,r=[];for(e=0;e<64;e+=4)r[e>>2]=t[e]+(t[e+1]<<8)+(t[e+2]<<16)+(t[e+3]<<24);return r}function i(t){var n,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(n=64;n<=f;n+=64)e(c,r(t.substring(n-64,n)));for(t=t.substring(n-64),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],n=0;n<i;n+=1)a[n>>2]|=t.charCodeAt(n)<<(n%4<<3);if(a[n>>2]|=128<<(n%4<<3),n>55)for(e(c,a),n=0;n<16;n+=1)a[n]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function a(t){var r,i,a,u,o,s,f=t.length,c=[1732584193,-271733879,-1732584194,271733878];for(r=64;r<=f;r+=64)e(c,n(t.subarray(r-64,r)));for(t=r-64<f?t.subarray(r-64):new Uint8Array(0),i=t.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],r=0;r<i;r+=1)a[r>>2]|=t[r]<<(r%4<<3);if(a[r>>2]|=128<<(r%4<<3),r>55)for(e(c,a),r=0;r<16;r+=1)a[r]=0;return u=8*f,u=u.toString(16).match(/(.*?)(.{0,8})$/),o=parseInt(u[2],16),s=parseInt(u[1],16)||0,a[14]=o,a[15]=s,e(c,a),c}function u(t){var e,r="";for(e=0;e<4;e+=1)r+=p[t>>8*e+4&15]+p[t>>8*e&15];return r}function o(t){var e;for(e=0;e<t.length;e+=1)t[e]=u(t[e]);return t.join("")}function s(t){return/[\u0080-\uFFFF]/.test(t)&&(t=unescape(encodeURIComponent(t))),t}function f(t,e){var r,n=t.length,i=new ArrayBuffer(n),a=new Uint8Array(i);for(r=0;r<n;r+=1)a[r]=t.charCodeAt(r);return e?a:i}function c(t){return String.fromCharCode.apply(null,new Uint8Array(t))}function h(t,e,r){var n=new Uint8Array(t.byteLength+e.byteLength);return n.set(new Uint8Array(t)),n.set(new Uint8Array(e),t.byteLength),r?n:n.buffer}function l(t){var e,r=[],n=t.length;for(e=0;e<n-1;e+=2)r.push(parseInt(t.substr(e,2),16));return String.fromCharCode.apply(String,r)}function d(){this.reset()}var p=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];return"5d41402abc4b2a76b9719d911017c592"!==o(i("hello"))&&function(t,e){var r=(65535&t)+(65535&e);return(t>>16)+(e>>16)+(r>>16)<<16|65535&r},"undefined"==typeof ArrayBuffer||ArrayBuffer.prototype.slice||function(){function e(t,e){return t=0|t||0,t<0?Math.max(t+e,0):Math.min(t,e)}ArrayBuffer.prototype.slice=function(r,n){var i,a,u,o,s=this.byteLength,f=e(r,s),c=s;return n!==t&&(c=e(n,s)),f>c?new ArrayBuffer(0):(i=c-f,a=new ArrayBuffer(i),u=new Uint8Array(a),o=new Uint8Array(this,f,i),u.set(o),a)}}(),d.prototype.append=function(t){return this.appendBinary(s(t)),this},d.prototype.appendBinary=function(t){this._buff+=t,this._length+=t.length;var n,i=this._buff.length;for(n=64;n<=i;n+=64)e(this._hash,r(this._buff.substring(n-64,n)));return this._buff=this._buff.substring(n-64),this},d.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n.charCodeAt(e)<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.prototype.reset=function(){return this._buff="",this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.prototype.getState=function(){return{buff:this._buff,length:this._length,hash:this._hash}},d.prototype.setState=function(t){return this._buff=t.buff,this._length=t.length,this._hash=t.hash,this},d.prototype.destroy=function(){delete this._hash,delete this._buff,delete this._length},d.prototype._finish=function(t,r){var n,i,a,u=r;if(t[u>>2]|=128<<(u%4<<3),u>55)for(e(this._hash,t),u=0;u<16;u+=1)t[u]=0;n=8*this._length,n=n.toString(16).match(/(.*?)(.{0,8})$/),i=parseInt(n[2],16),a=parseInt(n[1],16)||0,t[14]=i,t[15]=a,e(this._hash,t)},d.hash=function(t,e){return d.hashBinary(s(t),e)},d.hashBinary=function(t,e){var r=i(t),n=o(r);return e?l(n):n},d.ArrayBuffer=function(){this.reset()},d.ArrayBuffer.prototype.append=function(t){var r,i=h(this._buff.buffer,t,!0),a=i.length;for(this._length+=t.byteLength,r=64;r<=a;r+=64)e(this._hash,n(i.subarray(r-64,r)));return this._buff=r-64<a?new Uint8Array(i.buffer.slice(r-64)):new Uint8Array(0),this},d.ArrayBuffer.prototype.end=function(t){var e,r,n=this._buff,i=n.length,a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(e=0;e<i;e+=1)a[e>>2]|=n[e]<<(e%4<<3);return this._finish(a,i),r=o(this._hash),t&&(r=l(r)),this.reset(),r},d.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._hash=[1732584193,-271733879,-1732584194,271733878],this},d.ArrayBuffer.prototype.getState=function(){var t=d.prototype.getState.call(this);return t.buff=c(t.buff),t},d.ArrayBuffer.prototype.setState=function(t){return t.buff=f(t.buff,!0),d.prototype.setState.call(this,t)},d.ArrayBuffer.prototype.destroy=d.prototype.destroy,d.ArrayBuffer.prototype._finish=d.prototype._finish,d.ArrayBuffer.hash=function(t,e){var r=a(new Uint8Array(t)),n=o(r);return e?l(n):n},d})},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return u});var i=r(0),a=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),u=function(){function t(e,r,a){var u=this;n(this,t),this.file=e,this.attributes={filename:e.name,content_type:e.type,byte_size:e.size,checksum:r},this.xhr=new XMLHttpRequest,this.xhr.open("POST",a,!0),this.xhr.responseType="json",this.xhr.setRequestHeader("Content-Type","application/json"),this.xhr.setRequestHeader("Accept","application/json"),this.xhr.setRequestHeader("X-Requested-With","XMLHttpRequest"),this.xhr.setRequestHeader("X-CSRF-Token",Object(i.d)("csrf-token")),this.xhr.addEventListener("load",function(t){return u.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return u.requestDidError(t)})}return a(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(JSON.stringify({blob:this.attributes}))}},{key:"requestDidLoad",value:function(t){if(this.status>=200&&this.status<300){var e=this.response,r=e.direct_upload;delete e.direct_upload,this.attributes=e,this.directUploadData=r,this.callback(null,this.toJSON())}else this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error creating Blob for "'+this.file.name+'". Status: '+this.status)}},{key:"toJSON",value:function(){var t={};for(var e in this.attributes)t[e]=this.attributes[e];return t}},{key:"status",get:function(){return this.xhr.status}},{key:"response",get:function(){var t=this.xhr,e=t.responseType,r=t.response;return"json"==e?r:JSON.parse(r)}}]),t}()},function(t,e,r){"use strict";function n(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}r.d(e,"a",function(){return a});var i=function(){function t(t,e){for(var r=0;r<e.length;r++){var n=e[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,r,n){return r&&t(e.prototype,r),n&&t(e,n),e}}(),a=function(){function t(e){var r=this;n(this,t),this.blob=e,this.file=e.file;var i=e.directUploadData,a=i.url,u=i.headers;this.xhr=new XMLHttpRequest,this.xhr.open("PUT",a,!0),this.xhr.responseType="text";for(var o in u)this.xhr.setRequestHeader(o,u[o]);this.xhr.addEventListener("load",function(t){return r.requestDidLoad(t)}),this.xhr.addEventListener("error",function(t){return r.requestDidError(t)})}return i(t,[{key:"create",value:function(t){this.callback=t,this.xhr.send(this.file.slice())}},{key:"requestDidLoad",value:function(t){var e=this.xhr,r=e.status,n=e.response;r>=200&&r<300?this.callback(null,n):this.requestDidError(t)}},{key:"requestDidError",value:function(t){this.callback('Error storing "'+this.file.name+'". Status: '+this.xhr.status)}}]),t}()}])});
+(function(global, factory) {
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : factory(global.ActiveStorage = {});
+})(this, function(exports) {
+ "use strict";
+ function createCommonjsModule(fn, module) {
+ return module = {
+ exports: {}
+ }, fn(module, module.exports), module.exports;
+ }
+ var sparkMd5 = createCommonjsModule(function(module, exports) {
+ (function(factory) {
+ {
+ module.exports = factory();
+ }
+ })(function(undefined) {
+ var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ];
+ function md5cycle(x, k) {
+ var a = x[0], b = x[1], c = x[2], d = x[3];
+ a += (b & c | ~b & d) + k[0] - 680876936 | 0;
+ a = (a << 7 | a >>> 25) + b | 0;
+ d += (a & b | ~a & c) + k[1] - 389564586 | 0;
+ d = (d << 12 | d >>> 20) + a | 0;
+ c += (d & a | ~d & b) + k[2] + 606105819 | 0;
+ c = (c << 17 | c >>> 15) + d | 0;
+ b += (c & d | ~c & a) + k[3] - 1044525330 | 0;
+ b = (b << 22 | b >>> 10) + c | 0;
+ a += (b & c | ~b & d) + k[4] - 176418897 | 0;
+ a = (a << 7 | a >>> 25) + b | 0;
+ d += (a & b | ~a & c) + k[5] + 1200080426 | 0;
+ d = (d << 12 | d >>> 20) + a | 0;
+ c += (d & a | ~d & b) + k[6] - 1473231341 | 0;
+ c = (c << 17 | c >>> 15) + d | 0;
+ b += (c & d | ~c & a) + k[7] - 45705983 | 0;
+ b = (b << 22 | b >>> 10) + c | 0;
+ a += (b & c | ~b & d) + k[8] + 1770035416 | 0;
+ a = (a << 7 | a >>> 25) + b | 0;
+ d += (a & b | ~a & c) + k[9] - 1958414417 | 0;
+ d = (d << 12 | d >>> 20) + a | 0;
+ c += (d & a | ~d & b) + k[10] - 42063 | 0;
+ c = (c << 17 | c >>> 15) + d | 0;
+ b += (c & d | ~c & a) + k[11] - 1990404162 | 0;
+ b = (b << 22 | b >>> 10) + c | 0;
+ a += (b & c | ~b & d) + k[12] + 1804603682 | 0;
+ a = (a << 7 | a >>> 25) + b | 0;
+ d += (a & b | ~a & c) + k[13] - 40341101 | 0;
+ d = (d << 12 | d >>> 20) + a | 0;
+ c += (d & a | ~d & b) + k[14] - 1502002290 | 0;
+ c = (c << 17 | c >>> 15) + d | 0;
+ b += (c & d | ~c & a) + k[15] + 1236535329 | 0;
+ b = (b << 22 | b >>> 10) + c | 0;
+ a += (b & d | c & ~d) + k[1] - 165796510 | 0;
+ a = (a << 5 | a >>> 27) + b | 0;
+ d += (a & c | b & ~c) + k[6] - 1069501632 | 0;
+ d = (d << 9 | d >>> 23) + a | 0;
+ c += (d & b | a & ~b) + k[11] + 643717713 | 0;
+ c = (c << 14 | c >>> 18) + d | 0;
+ b += (c & a | d & ~a) + k[0] - 373897302 | 0;
+ b = (b << 20 | b >>> 12) + c | 0;
+ a += (b & d | c & ~d) + k[5] - 701558691 | 0;
+ a = (a << 5 | a >>> 27) + b | 0;
+ d += (a & c | b & ~c) + k[10] + 38016083 | 0;
+ d = (d << 9 | d >>> 23) + a | 0;
+ c += (d & b | a & ~b) + k[15] - 660478335 | 0;
+ c = (c << 14 | c >>> 18) + d | 0;
+ b += (c & a | d & ~a) + k[4] - 405537848 | 0;
+ b = (b << 20 | b >>> 12) + c | 0;
+ a += (b & d | c & ~d) + k[9] + 568446438 | 0;
+ a = (a << 5 | a >>> 27) + b | 0;
+ d += (a & c | b & ~c) + k[14] - 1019803690 | 0;
+ d = (d << 9 | d >>> 23) + a | 0;
+ c += (d & b | a & ~b) + k[3] - 187363961 | 0;
+ c = (c << 14 | c >>> 18) + d | 0;
+ b += (c & a | d & ~a) + k[8] + 1163531501 | 0;
+ b = (b << 20 | b >>> 12) + c | 0;
+ a += (b & d | c & ~d) + k[13] - 1444681467 | 0;
+ a = (a << 5 | a >>> 27) + b | 0;
+ d += (a & c | b & ~c) + k[2] - 51403784 | 0;
+ d = (d << 9 | d >>> 23) + a | 0;
+ c += (d & b | a & ~b) + k[7] + 1735328473 | 0;
+ c = (c << 14 | c >>> 18) + d | 0;
+ b += (c & a | d & ~a) + k[12] - 1926607734 | 0;
+ b = (b << 20 | b >>> 12) + c | 0;
+ a += (b ^ c ^ d) + k[5] - 378558 | 0;
+ a = (a << 4 | a >>> 28) + b | 0;
+ d += (a ^ b ^ c) + k[8] - 2022574463 | 0;
+ d = (d << 11 | d >>> 21) + a | 0;
+ c += (d ^ a ^ b) + k[11] + 1839030562 | 0;
+ c = (c << 16 | c >>> 16) + d | 0;
+ b += (c ^ d ^ a) + k[14] - 35309556 | 0;
+ b = (b << 23 | b >>> 9) + c | 0;
+ a += (b ^ c ^ d) + k[1] - 1530992060 | 0;
+ a = (a << 4 | a >>> 28) + b | 0;
+ d += (a ^ b ^ c) + k[4] + 1272893353 | 0;
+ d = (d << 11 | d >>> 21) + a | 0;
+ c += (d ^ a ^ b) + k[7] - 155497632 | 0;
+ c = (c << 16 | c >>> 16) + d | 0;
+ b += (c ^ d ^ a) + k[10] - 1094730640 | 0;
+ b = (b << 23 | b >>> 9) + c | 0;
+ a += (b ^ c ^ d) + k[13] + 681279174 | 0;
+ a = (a << 4 | a >>> 28) + b | 0;
+ d += (a ^ b ^ c) + k[0] - 358537222 | 0;
+ d = (d << 11 | d >>> 21) + a | 0;
+ c += (d ^ a ^ b) + k[3] - 722521979 | 0;
+ c = (c << 16 | c >>> 16) + d | 0;
+ b += (c ^ d ^ a) + k[6] + 76029189 | 0;
+ b = (b << 23 | b >>> 9) + c | 0;
+ a += (b ^ c ^ d) + k[9] - 640364487 | 0;
+ a = (a << 4 | a >>> 28) + b | 0;
+ d += (a ^ b ^ c) + k[12] - 421815835 | 0;
+ d = (d << 11 | d >>> 21) + a | 0;
+ c += (d ^ a ^ b) + k[15] + 530742520 | 0;
+ c = (c << 16 | c >>> 16) + d | 0;
+ b += (c ^ d ^ a) + k[2] - 995338651 | 0;
+ b = (b << 23 | b >>> 9) + c | 0;
+ a += (c ^ (b | ~d)) + k[0] - 198630844 | 0;
+ a = (a << 6 | a >>> 26) + b | 0;
+ d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0;
+ d = (d << 10 | d >>> 22) + a | 0;
+ c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0;
+ c = (c << 15 | c >>> 17) + d | 0;
+ b += (d ^ (c | ~a)) + k[5] - 57434055 | 0;
+ b = (b << 21 | b >>> 11) + c | 0;
+ a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0;
+ a = (a << 6 | a >>> 26) + b | 0;
+ d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0;
+ d = (d << 10 | d >>> 22) + a | 0;
+ c += (a ^ (d | ~b)) + k[10] - 1051523 | 0;
+ c = (c << 15 | c >>> 17) + d | 0;
+ b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0;
+ b = (b << 21 | b >>> 11) + c | 0;
+ a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0;
+ a = (a << 6 | a >>> 26) + b | 0;
+ d += (b ^ (a | ~c)) + k[15] - 30611744 | 0;
+ d = (d << 10 | d >>> 22) + a | 0;
+ c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0;
+ c = (c << 15 | c >>> 17) + d | 0;
+ b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0;
+ b = (b << 21 | b >>> 11) + c | 0;
+ a += (c ^ (b | ~d)) + k[4] - 145523070 | 0;
+ a = (a << 6 | a >>> 26) + b | 0;
+ d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0;
+ d = (d << 10 | d >>> 22) + a | 0;
+ c += (a ^ (d | ~b)) + k[2] + 718787259 | 0;
+ c = (c << 15 | c >>> 17) + d | 0;
+ b += (d ^ (c | ~a)) + k[9] - 343485551 | 0;
+ b = (b << 21 | b >>> 11) + c | 0;
+ x[0] = a + x[0] | 0;
+ x[1] = b + x[1] | 0;
+ x[2] = c + x[2] | 0;
+ x[3] = d + x[3] | 0;
+ }
+ function md5blk(s) {
+ var md5blks = [], i;
+ for (i = 0; i < 64; i += 4) {
+ md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
+ }
+ return md5blks;
+ }
+ function md5blk_array(a) {
+ var md5blks = [], i;
+ for (i = 0; i < 64; i += 4) {
+ md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
+ }
+ return md5blks;
+ }
+ function md51(s) {
+ var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi;
+ for (i = 64; i <= n; i += 64) {
+ md5cycle(state, md5blk(s.substring(i - 64, i)));
+ }
+ s = s.substring(i - 64);
+ length = s.length;
+ tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+ for (i = 0; i < length; i += 1) {
+ tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
+ }
+ tail[i >> 2] |= 128 << (i % 4 << 3);
+ if (i > 55) {
+ md5cycle(state, tail);
+ for (i = 0; i < 16; i += 1) {
+ tail[i] = 0;
+ }
+ }
+ tmp = n * 8;
+ tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
+ lo = parseInt(tmp[2], 16);
+ hi = parseInt(tmp[1], 16) || 0;
+ tail[14] = lo;
+ tail[15] = hi;
+ md5cycle(state, tail);
+ return state;
+ }
+ function md51_array(a) {
+ var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi;
+ for (i = 64; i <= n; i += 64) {
+ md5cycle(state, md5blk_array(a.subarray(i - 64, i)));
+ }
+ a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0);
+ length = a.length;
+ tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+ for (i = 0; i < length; i += 1) {
+ tail[i >> 2] |= a[i] << (i % 4 << 3);
+ }
+ tail[i >> 2] |= 128 << (i % 4 << 3);
+ if (i > 55) {
+ md5cycle(state, tail);
+ for (i = 0; i < 16; i += 1) {
+ tail[i] = 0;
+ }
+ }
+ tmp = n * 8;
+ tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
+ lo = parseInt(tmp[2], 16);
+ hi = parseInt(tmp[1], 16) || 0;
+ tail[14] = lo;
+ tail[15] = hi;
+ md5cycle(state, tail);
+ return state;
+ }
+ function rhex(n) {
+ var s = "", j;
+ for (j = 0; j < 4; j += 1) {
+ s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
+ }
+ return s;
+ }
+ function hex(x) {
+ var i;
+ for (i = 0; i < x.length; i += 1) {
+ x[i] = rhex(x[i]);
+ }
+ return x.join("");
+ }
+ if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ;
+ if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) {
+ (function() {
+ function clamp(val, length) {
+ val = val | 0 || 0;
+ if (val < 0) {
+ return Math.max(val + length, 0);
+ }
+ return Math.min(val, length);
+ }
+ ArrayBuffer.prototype.slice = function(from, to) {
+ var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray;
+ if (to !== undefined) {
+ end = clamp(to, length);
+ }
+ if (begin > end) {
+ return new ArrayBuffer(0);
+ }
+ num = end - begin;
+ target = new ArrayBuffer(num);
+ targetArray = new Uint8Array(target);
+ sourceArray = new Uint8Array(this, begin, num);
+ targetArray.set(sourceArray);
+ return target;
+ };
+ })();
+ }
+ function toUtf8(str) {
+ if (/[\u0080-\uFFFF]/.test(str)) {
+ str = unescape(encodeURIComponent(str));
+ }
+ return str;
+ }
+ function utf8Str2ArrayBuffer(str, returnUInt8Array) {
+ var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i;
+ for (i = 0; i < length; i += 1) {
+ arr[i] = str.charCodeAt(i);
+ }
+ return returnUInt8Array ? arr : buff;
+ }
+ function arrayBuffer2Utf8Str(buff) {
+ return String.fromCharCode.apply(null, new Uint8Array(buff));
+ }
+ function concatenateArrayBuffers(first, second, returnUInt8Array) {
+ var result = new Uint8Array(first.byteLength + second.byteLength);
+ result.set(new Uint8Array(first));
+ result.set(new Uint8Array(second), first.byteLength);
+ return returnUInt8Array ? result : result.buffer;
+ }
+ function hexToBinaryString(hex) {
+ var bytes = [], length = hex.length, x;
+ for (x = 0; x < length - 1; x += 2) {
+ bytes.push(parseInt(hex.substr(x, 2), 16));
+ }
+ return String.fromCharCode.apply(String, bytes);
+ }
+ function SparkMD5() {
+ this.reset();
+ }
+ SparkMD5.prototype.append = function(str) {
+ this.appendBinary(toUtf8(str));
+ return this;
+ };
+ SparkMD5.prototype.appendBinary = function(contents) {
+ this._buff += contents;
+ this._length += contents.length;
+ var length = this._buff.length, i;
+ for (i = 64; i <= length; i += 64) {
+ md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)));
+ }
+ this._buff = this._buff.substring(i - 64);
+ return this;
+ };
+ SparkMD5.prototype.end = function(raw) {
+ var buff = this._buff, length = buff.length, i, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], ret;
+ for (i = 0; i < length; i += 1) {
+ tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3);
+ }
+ this._finish(tail, length);
+ ret = hex(this._hash);
+ if (raw) {
+ ret = hexToBinaryString(ret);
+ }
+ this.reset();
+ return ret;
+ };
+ SparkMD5.prototype.reset = function() {
+ this._buff = "";
+ this._length = 0;
+ this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ];
+ return this;
+ };
+ SparkMD5.prototype.getState = function() {
+ return {
+ buff: this._buff,
+ length: this._length,
+ hash: this._hash
+ };
+ };
+ SparkMD5.prototype.setState = function(state) {
+ this._buff = state.buff;
+ this._length = state.length;
+ this._hash = state.hash;
+ return this;
+ };
+ SparkMD5.prototype.destroy = function() {
+ delete this._hash;
+ delete this._buff;
+ delete this._length;
+ };
+ SparkMD5.prototype._finish = function(tail, length) {
+ var i = length, tmp, lo, hi;
+ tail[i >> 2] |= 128 << (i % 4 << 3);
+ if (i > 55) {
+ md5cycle(this._hash, tail);
+ for (i = 0; i < 16; i += 1) {
+ tail[i] = 0;
+ }
+ }
+ tmp = this._length * 8;
+ tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
+ lo = parseInt(tmp[2], 16);
+ hi = parseInt(tmp[1], 16) || 0;
+ tail[14] = lo;
+ tail[15] = hi;
+ md5cycle(this._hash, tail);
+ };
+ SparkMD5.hash = function(str, raw) {
+ return SparkMD5.hashBinary(toUtf8(str), raw);
+ };
+ SparkMD5.hashBinary = function(content, raw) {
+ var hash = md51(content), ret = hex(hash);
+ return raw ? hexToBinaryString(ret) : ret;
+ };
+ SparkMD5.ArrayBuffer = function() {
+ this.reset();
+ };
+ SparkMD5.ArrayBuffer.prototype.append = function(arr) {
+ var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i;
+ this._length += arr.byteLength;
+ for (i = 64; i <= length; i += 64) {
+ md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));
+ }
+ this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0);
+ return this;
+ };
+ SparkMD5.ArrayBuffer.prototype.end = function(raw) {
+ var buff = this._buff, length = buff.length, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], i, ret;
+ for (i = 0; i < length; i += 1) {
+ tail[i >> 2] |= buff[i] << (i % 4 << 3);
+ }
+ this._finish(tail, length);
+ ret = hex(this._hash);
+ if (raw) {
+ ret = hexToBinaryString(ret);
+ }
+ this.reset();
+ return ret;
+ };
+ SparkMD5.ArrayBuffer.prototype.reset = function() {
+ this._buff = new Uint8Array(0);
+ this._length = 0;
+ this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ];
+ return this;
+ };
+ SparkMD5.ArrayBuffer.prototype.getState = function() {
+ var state = SparkMD5.prototype.getState.call(this);
+ state.buff = arrayBuffer2Utf8Str(state.buff);
+ return state;
+ };
+ SparkMD5.ArrayBuffer.prototype.setState = function(state) {
+ state.buff = utf8Str2ArrayBuffer(state.buff, true);
+ return SparkMD5.prototype.setState.call(this, state);
+ };
+ SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;
+ SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;
+ SparkMD5.ArrayBuffer.hash = function(arr, raw) {
+ var hash = md51_array(new Uint8Array(arr)), ret = hex(hash);
+ return raw ? hexToBinaryString(ret) : ret;
+ };
+ return SparkMD5;
+ });
+ });
+ var classCallCheck = function(instance, Constructor) {
+ if (!(instance instanceof Constructor)) {
+ throw new TypeError("Cannot call a class as a function");
+ }
+ };
+ var createClass = function() {
+ function defineProperties(target, props) {
+ for (var i = 0; i < props.length; i++) {
+ var descriptor = props[i];
+ descriptor.enumerable = descriptor.enumerable || false;
+ descriptor.configurable = true;
+ if ("value" in descriptor) descriptor.writable = true;
+ Object.defineProperty(target, descriptor.key, descriptor);
+ }
+ }
+ return function(Constructor, protoProps, staticProps) {
+ if (protoProps) defineProperties(Constructor.prototype, protoProps);
+ if (staticProps) defineProperties(Constructor, staticProps);
+ return Constructor;
+ };
+ }();
+ var fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
+ var FileChecksum = function() {
+ createClass(FileChecksum, null, [ {
+ key: "create",
+ value: function create(file, callback) {
+ var instance = new FileChecksum(file);
+ instance.create(callback);
+ }
+ } ]);
+ function FileChecksum(file) {
+ classCallCheck(this, FileChecksum);
+ this.file = file;
+ this.chunkSize = 2097152;
+ this.chunkCount = Math.ceil(this.file.size / this.chunkSize);
+ this.chunkIndex = 0;
+ }
+ createClass(FileChecksum, [ {
+ key: "create",
+ value: function create(callback) {
+ var _this = this;
+ this.callback = callback;
+ this.md5Buffer = new sparkMd5.ArrayBuffer();
+ this.fileReader = new FileReader();
+ this.fileReader.addEventListener("load", function(event) {
+ return _this.fileReaderDidLoad(event);
+ });
+ this.fileReader.addEventListener("error", function(event) {
+ return _this.fileReaderDidError(event);
+ });
+ this.readNextChunk();
+ }
+ }, {
+ key: "fileReaderDidLoad",
+ value: function fileReaderDidLoad(event) {
+ this.md5Buffer.append(event.target.result);
+ if (!this.readNextChunk()) {
+ var binaryDigest = this.md5Buffer.end(true);
+ var base64digest = btoa(binaryDigest);
+ this.callback(null, base64digest);
+ }
+ }
+ }, {
+ key: "fileReaderDidError",
+ value: function fileReaderDidError(event) {
+ this.callback("Error reading " + this.file.name);
+ }
+ }, {
+ key: "readNextChunk",
+ value: function readNextChunk() {
+ if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) {
+ var start = this.chunkIndex * this.chunkSize;
+ var end = Math.min(start + this.chunkSize, this.file.size);
+ var bytes = fileSlice.call(this.file, start, end);
+ this.fileReader.readAsArrayBuffer(bytes);
+ this.chunkIndex++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } ]);
+ return FileChecksum;
+ }();
+ function getMetaValue(name) {
+ var element = findElement(document.head, 'meta[name="' + name + '"]');
+ if (element) {
+ return element.getAttribute("content");
+ }
+ }
+ function findElements(root, selector) {
+ if (typeof root == "string") {
+ selector = root;
+ root = document;
+ }
+ var elements = root.querySelectorAll(selector);
+ return toArray$1(elements);
+ }
+ function findElement(root, selector) {
+ if (typeof root == "string") {
+ selector = root;
+ root = document;
+ }
+ return root.querySelector(selector);
+ }
+ function dispatchEvent(element, type) {
+ var eventInit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
+ var disabled = element.disabled;
+ var bubbles = eventInit.bubbles, cancelable = eventInit.cancelable, detail = eventInit.detail;
+ var event = document.createEvent("Event");
+ event.initEvent(type, bubbles || true, cancelable || true);
+ event.detail = detail || {};
+ try {
+ element.disabled = false;
+ element.dispatchEvent(event);
+ } finally {
+ element.disabled = disabled;
+ }
+ return event;
+ }
+ function toArray$1(value) {
+ if (Array.isArray(value)) {
+ return value;
+ } else if (Array.from) {
+ return Array.from(value);
+ } else {
+ return [].slice.call(value);
+ }
+ }
+ var BlobRecord = function() {
+ function BlobRecord(file, checksum, url) {
+ var _this = this;
+ classCallCheck(this, BlobRecord);
+ this.file = file;
+ this.attributes = {
+ filename: file.name,
+ content_type: file.type,
+ byte_size: file.size,
+ checksum: checksum
+ };
+ this.xhr = new XMLHttpRequest();
+ this.xhr.open("POST", url, true);
+ this.xhr.responseType = "json";
+ this.xhr.setRequestHeader("Content-Type", "application/json");
+ this.xhr.setRequestHeader("Accept", "application/json");
+ this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+ this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token"));
+ this.xhr.addEventListener("load", function(event) {
+ return _this.requestDidLoad(event);
+ });
+ this.xhr.addEventListener("error", function(event) {
+ return _this.requestDidError(event);
+ });
+ }
+ createClass(BlobRecord, [ {
+ key: "create",
+ value: function create(callback) {
+ this.callback = callback;
+ this.xhr.send(JSON.stringify({
+ blob: this.attributes
+ }));
+ }
+ }, {
+ key: "requestDidLoad",
+ value: function requestDidLoad(event) {
+ if (this.status >= 200 && this.status < 300) {
+ var response = this.response;
+ var direct_upload = response.direct_upload;
+ delete response.direct_upload;
+ this.attributes = response;
+ this.directUploadData = direct_upload;
+ this.callback(null, this.toJSON());
+ } else {
+ this.requestDidError(event);
+ }
+ }
+ }, {
+ key: "requestDidError",
+ value: function requestDidError(event) {
+ this.callback('Error creating Blob for "' + this.file.name + '". Status: ' + this.status);
+ }
+ }, {
+ key: "toJSON",
+ value: function toJSON() {
+ var result = {};
+ for (var key in this.attributes) {
+ result[key] = this.attributes[key];
+ }
+ return result;
+ }
+ }, {
+ key: "status",
+ get: function get$$1() {
+ return this.xhr.status;
+ }
+ }, {
+ key: "response",
+ get: function get$$1() {
+ var _xhr = this.xhr, responseType = _xhr.responseType, response = _xhr.response;
+ if (responseType == "json") {
+ return response;
+ } else {
+ return JSON.parse(response);
+ }
+ }
+ } ]);
+ return BlobRecord;
+ }();
+ var BlobUpload = function() {
+ function BlobUpload(blob) {
+ var _this = this;
+ classCallCheck(this, BlobUpload);
+ this.blob = blob;
+ this.file = blob.file;
+ var _blob$directUploadDat = blob.directUploadData, url = _blob$directUploadDat.url, headers = _blob$directUploadDat.headers;
+ this.xhr = new XMLHttpRequest();
+ this.xhr.open("PUT", url, true);
+ this.xhr.responseType = "text";
+ for (var key in headers) {
+ this.xhr.setRequestHeader(key, headers[key]);
+ }
+ this.xhr.addEventListener("load", function(event) {
+ return _this.requestDidLoad(event);
+ });
+ this.xhr.addEventListener("error", function(event) {
+ return _this.requestDidError(event);
+ });
+ }
+ createClass(BlobUpload, [ {
+ key: "create",
+ value: function create(callback) {
+ this.callback = callback;
+ this.xhr.send(this.file.slice());
+ }
+ }, {
+ key: "requestDidLoad",
+ value: function requestDidLoad(event) {
+ var _xhr = this.xhr, status = _xhr.status, response = _xhr.response;
+ if (status >= 200 && status < 300) {
+ this.callback(null, response);
+ } else {
+ this.requestDidError(event);
+ }
+ }
+ }, {
+ key: "requestDidError",
+ value: function requestDidError(event) {
+ this.callback('Error storing "' + this.file.name + '". Status: ' + this.xhr.status);
+ }
+ } ]);
+ return BlobUpload;
+ }();
+ var id = 0;
+ var DirectUpload = function() {
+ function DirectUpload(file, url, delegate) {
+ classCallCheck(this, DirectUpload);
+ this.id = ++id;
+ this.file = file;
+ this.url = url;
+ this.delegate = delegate;
+ }
+ createClass(DirectUpload, [ {
+ key: "create",
+ value: function create(callback) {
+ var _this = this;
+ FileChecksum.create(this.file, function(error, checksum) {
+ if (error) {
+ callback(error);
+ return;
+ }
+ var blob = new BlobRecord(_this.file, checksum, _this.url);
+ notify(_this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
+ blob.create(function(error) {
+ if (error) {
+ callback(error);
+ } else {
+ var upload = new BlobUpload(blob);
+ notify(_this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr);
+ upload.create(function(error) {
+ if (error) {
+ callback(error);
+ } else {
+ callback(null, blob.toJSON());
+ }
+ });
+ }
+ });
+ });
+ }
+ } ]);
+ return DirectUpload;
+ }();
+ function notify(object, methodName) {
+ if (object && typeof object[methodName] == "function") {
+ for (var _len = arguments.length, messages = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
+ messages[_key - 2] = arguments[_key];
+ }
+ return object[methodName].apply(object, messages);
+ }
+ }
+ var DirectUploadController = function() {
+ function DirectUploadController(input, file) {
+ classCallCheck(this, DirectUploadController);
+ this.input = input;
+ this.file = file;
+ this.directUpload = new DirectUpload(this.file, this.url, this);
+ this.dispatch("initialize");
+ }
+ createClass(DirectUploadController, [ {
+ key: "start",
+ value: function start(callback) {
+ var _this = this;
+ var hiddenInput = document.createElement("input");
+ hiddenInput.type = "hidden";
+ hiddenInput.name = this.input.name;
+ this.input.insertAdjacentElement("beforebegin", hiddenInput);
+ this.dispatch("start");
+ this.directUpload.create(function(error, attributes) {
+ if (error) {
+ hiddenInput.parentNode.removeChild(hiddenInput);
+ _this.dispatchError(error);
+ } else {
+ hiddenInput.value = attributes.signed_id;
+ }
+ _this.dispatch("end");
+ callback(error);
+ });
+ }
+ }, {
+ key: "uploadRequestDidProgress",
+ value: function uploadRequestDidProgress(event) {
+ var progress = event.loaded / event.total * 100;
+ if (progress) {
+ this.dispatch("progress", {
+ progress: progress
+ });
+ }
+ }
+ }, {
+ key: "dispatch",
+ value: function dispatch(name) {
+ var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ detail.file = this.file;
+ detail.id = this.directUpload.id;
+ return dispatchEvent(this.input, "direct-upload:" + name, {
+ detail: detail
+ });
+ }
+ }, {
+ key: "dispatchError",
+ value: function dispatchError(error) {
+ var event = this.dispatch("error", {
+ error: error
+ });
+ if (!event.defaultPrevented) {
+ alert(error);
+ }
+ }
+ }, {
+ key: "directUploadWillCreateBlobWithXHR",
+ value: function directUploadWillCreateBlobWithXHR(xhr) {
+ this.dispatch("before-blob-request", {
+ xhr: xhr
+ });
+ }
+ }, {
+ key: "directUploadWillStoreFileWithXHR",
+ value: function directUploadWillStoreFileWithXHR(xhr) {
+ var _this2 = this;
+ this.dispatch("before-storage-request", {
+ xhr: xhr
+ });
+ xhr.upload.addEventListener("progress", function(event) {
+ return _this2.uploadRequestDidProgress(event);
+ });
+ }
+ }, {
+ key: "url",
+ get: function get$$1() {
+ return this.input.getAttribute("data-direct-upload-url");
+ }
+ } ]);
+ return DirectUploadController;
+ }();
+ var inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])";
+ var DirectUploadsController = function() {
+ function DirectUploadsController(form) {
+ classCallCheck(this, DirectUploadsController);
+ this.form = form;
+ this.inputs = findElements(form, inputSelector).filter(function(input) {
+ return input.files.length;
+ });
+ }
+ createClass(DirectUploadsController, [ {
+ key: "start",
+ value: function start(callback) {
+ var _this = this;
+ var controllers = this.createDirectUploadControllers();
+ var startNextController = function startNextController() {
+ var controller = controllers.shift();
+ if (controller) {
+ controller.start(function(error) {
+ if (error) {
+ callback(error);
+ _this.dispatch("end");
+ } else {
+ startNextController();
+ }
+ });
+ } else {
+ callback();
+ _this.dispatch("end");
+ }
+ };
+ this.dispatch("start");
+ startNextController();
+ }
+ }, {
+ key: "createDirectUploadControllers",
+ value: function createDirectUploadControllers() {
+ var controllers = [];
+ this.inputs.forEach(function(input) {
+ toArray$1(input.files).forEach(function(file) {
+ var controller = new DirectUploadController(input, file);
+ controllers.push(controller);
+ });
+ });
+ return controllers;
+ }
+ }, {
+ key: "dispatch",
+ value: function dispatch(name) {
+ var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
+ return dispatchEvent(this.form, "direct-uploads:" + name, {
+ detail: detail
+ });
+ }
+ } ]);
+ return DirectUploadsController;
+ }();
+ var processingAttribute = "data-direct-uploads-processing";
+ var submitButtonsByForm = new WeakMap();
+ var started = false;
+ function start() {
+ if (!started) {
+ started = true;
+ document.addEventListener("click", didClick, true);
+ document.addEventListener("submit", didSubmitForm);
+ document.addEventListener("ajax:before", didSubmitRemoteElement);
+ }
+ }
+ function didClick(event) {
+ var target = event.target;
+ if (target.tagName == "INPUT" && target.type == "submit" && target.form) {
+ submitButtonsByForm.set(target.form, target);
+ }
+ }
+ function didSubmitForm(event) {
+ handleFormSubmissionEvent(event);
+ }
+ function didSubmitRemoteElement(event) {
+ if (event.target.tagName == "FORM") {
+ handleFormSubmissionEvent(event);
+ }
+ }
+ function handleFormSubmissionEvent(event) {
+ var form = event.target;
+ if (form.hasAttribute(processingAttribute)) {
+ event.preventDefault();
+ return;
+ }
+ var controller = new DirectUploadsController(form);
+ var inputs = controller.inputs;
+ if (inputs.length) {
+ event.preventDefault();
+ form.setAttribute(processingAttribute, "");
+ inputs.forEach(disable);
+ controller.start(function(error) {
+ form.removeAttribute(processingAttribute);
+ if (error) {
+ inputs.forEach(enable);
+ } else {
+ submitForm(form);
+ }
+ });
+ }
+ }
+ function submitForm(form) {
+ var button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit]");
+ if (button) {
+ var _button = button, disabled = _button.disabled;
+ button.disabled = false;
+ button.focus();
+ button.click();
+ button.disabled = disabled;
+ } else {
+ button = document.createElement("input");
+ button.type = "submit";
+ button.style.display = "none";
+ form.appendChild(button);
+ button.click();
+ form.removeChild(button);
+ }
+ submitButtonsByForm.delete(form);
+ }
+ function disable(input) {
+ input.disabled = true;
+ }
+ function enable(input) {
+ input.disabled = false;
+ }
+ function autostart() {
+ if (window.ActiveStorage) {
+ start();
+ }
+ }
+ setTimeout(autostart, 1);
+ exports.start = start;
+ exports.DirectUpload = DirectUpload;
+ Object.defineProperty(exports, "__esModule", {
+ value: true
+ });
+});
diff --git a/activestorage/app/controllers/active_storage/base_controller.rb b/activestorage/app/controllers/active_storage/base_controller.rb
index 59312ac8df..b27d2bd8aa 100644
--- a/activestorage/app/controllers/active_storage/base_controller.rb
+++ b/activestorage/app/controllers/active_storage/base_controller.rb
@@ -1,10 +1,8 @@
# frozen_string_literal: true
-# The base controller for all ActiveStorage controllers.
+# The base class for all Active Storage controllers.
class ActiveStorage::BaseController < ActionController::Base
- protect_from_forgery with: :exception
+ include ActiveStorage::SetCurrent
- before_action do
- ActiveStorage::Current.host = request.base_url
- end
+ protect_from_forgery with: :exception
end
diff --git a/activestorage/app/controllers/active_storage/blobs_controller.rb b/activestorage/app/controllers/active_storage/blobs_controller.rb
index 92e54c386d..4fc3fbe824 100644
--- a/activestorage/app/controllers/active_storage/blobs_controller.rb
+++ b/activestorage/app/controllers/active_storage/blobs_controller.rb
@@ -8,7 +8,7 @@ class ActiveStorage::BlobsController < ActiveStorage::BaseController
include ActiveStorage::SetBlob
def show
- expires_in ActiveStorage::Blob.service.url_expires_in
+ expires_in ActiveStorage.service_urls_expire_in
redirect_to @blob.service_url(disposition: params[:disposition])
end
end
diff --git a/activestorage/app/controllers/active_storage/disk_controller.rb b/activestorage/app/controllers/active_storage/disk_controller.rb
index 7bc5eb3fdb..7bd641ab9a 100644
--- a/activestorage/app/controllers/active_storage/disk_controller.rb
+++ b/activestorage/app/controllers/active_storage/disk_controller.rb
@@ -9,11 +9,12 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
def show
if key = decode_verified_key
- send_data disk_service.download(key),
- disposition: params[:disposition], content_type: params[:content_type]
+ serve_file disk_service.path_for(key), content_type: params[:content_type], disposition: params[:disposition]
else
head :not_found
end
+ rescue Errno::ENOENT
+ head :not_found
end
def update
@@ -40,6 +41,20 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
ActiveStorage.verifier.verified(params[:encoded_key], purpose: :blob_key)
end
+ def serve_file(path, content_type:, disposition:)
+ Rack::File.new(nil).serving(request, path).tap do |(status, headers, body)|
+ self.status = status
+ self.response_body = body
+
+ headers.each do |name, value|
+ response.headers[name] = value
+ end
+
+ response.headers["Content-Type"] = content_type || DEFAULT_SEND_FILE_TYPE
+ response.headers["Content-Disposition"] = disposition || DEFAULT_SEND_FILE_DISPOSITION
+ end
+ end
+
def decode_verified_token
ActiveStorage.verifier.verified(params[:encoded_token], purpose: :blob_token)
diff --git a/activestorage/app/controllers/active_storage/representations_controller.rb b/activestorage/app/controllers/active_storage/representations_controller.rb
index ce9286db7d..98e11e5dbb 100644
--- a/activestorage/app/controllers/active_storage/representations_controller.rb
+++ b/activestorage/app/controllers/active_storage/representations_controller.rb
@@ -8,7 +8,7 @@ class ActiveStorage::RepresentationsController < ActiveStorage::BaseController
include ActiveStorage::SetBlob
def show
- expires_in ActiveStorage::Blob.service.url_expires_in
+ expires_in ActiveStorage.service_urls_expire_in
redirect_to @blob.representation(params[:variation_key]).processed.service_url(disposition: params[:disposition])
end
end
diff --git a/activestorage/app/controllers/concerns/active_storage/set_current.rb b/activestorage/app/controllers/concerns/active_storage/set_current.rb
new file mode 100644
index 0000000000..597afe7064
--- /dev/null
+++ b/activestorage/app/controllers/concerns/active_storage/set_current.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+# Sets the <tt>ActiveStorage::Current.host</tt> attribute, which the disk service uses to generate URLs.
+# Include this concern in custom controllers that call ActiveStorage::Blob#service_url,
+# ActiveStorage::Variant#service_url, or ActiveStorage::Preview#service_url so the disk service can
+# generate URLs using the same host, protocol, and base path as the current request.
+module ActiveStorage::SetCurrent
+ extend ActiveSupport::Concern
+
+ included do
+ before_action do
+ ActiveStorage::Current.host = request.base_url
+ end
+ end
+end
diff --git a/activestorage/app/javascript/activestorage/file_checksum.js b/activestorage/app/javascript/activestorage/file_checksum.js
index ffaec1a128..a9dbef69ea 100644
--- a/activestorage/app/javascript/activestorage/file_checksum.js
+++ b/activestorage/app/javascript/activestorage/file_checksum.js
@@ -39,7 +39,7 @@ export class FileChecksum {
}
readNextChunk() {
- if (this.chunkIndex < this.chunkCount) {
+ if (this.chunkIndex < this.chunkCount || (this.chunkIndex == 0 && this.chunkCount == 0)) {
const start = this.chunkIndex * this.chunkSize
const end = Math.min(start + this.chunkSize, this.file.size)
const bytes = fileSlice.call(this.file, start, end)
diff --git a/activestorage/app/javascript/activestorage/ujs.js b/activestorage/app/javascript/activestorage/ujs.js
index 08c535470d..f5353389ef 100644
--- a/activestorage/app/javascript/activestorage/ujs.js
+++ b/activestorage/app/javascript/activestorage/ujs.js
@@ -2,16 +2,25 @@ import { DirectUploadsController } from "./direct_uploads_controller"
import { findElement } from "./helpers"
const processingAttribute = "data-direct-uploads-processing"
+const submitButtonsByForm = new WeakMap
let started = false
export function start() {
if (!started) {
started = true
+ document.addEventListener("click", didClick, true)
document.addEventListener("submit", didSubmitForm)
document.addEventListener("ajax:before", didSubmitRemoteElement)
}
}
+function didClick(event) {
+ const { target } = event
+ if (target.tagName == "INPUT" && target.type == "submit" && target.form) {
+ submitButtonsByForm.set(target.form, target)
+ }
+}
+
function didSubmitForm(event) {
handleFormSubmissionEvent(event)
}
@@ -49,7 +58,8 @@ function handleFormSubmissionEvent(event) {
}
function submitForm(form) {
- let button = findElement(form, "input[type=submit]")
+ let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit]")
+
if (button) {
const { disabled } = button
button.disabled = false
@@ -64,6 +74,7 @@ function submitForm(form) {
button.click()
form.removeChild(button)
}
+ submitButtonsByForm.delete(form)
}
function disable(input) {
diff --git a/activestorage/app/jobs/active_storage/analyze_job.rb b/activestorage/app/jobs/active_storage/analyze_job.rb
index 2a952f9f74..804ee4557a 100644
--- a/activestorage/app/jobs/active_storage/analyze_job.rb
+++ b/activestorage/app/jobs/active_storage/analyze_job.rb
@@ -2,6 +2,8 @@
# Provides asynchronous analysis of ActiveStorage::Blob records via ActiveStorage::Blob#analyze_later.
class ActiveStorage::AnalyzeJob < ActiveStorage::BaseJob
+ retry_on ActiveStorage::IntegrityError, attempts: 10, wait: :exponentially_longer
+
def perform(blob)
blob.analyze
end
diff --git a/activestorage/app/jobs/active_storage/purge_job.rb b/activestorage/app/jobs/active_storage/purge_job.rb
index 98874d2250..2604977bf1 100644
--- a/activestorage/app/jobs/active_storage/purge_job.rb
+++ b/activestorage/app/jobs/active_storage/purge_job.rb
@@ -2,8 +2,8 @@
# Provides asynchronous purging of ActiveStorage::Blob records via ActiveStorage::Blob#purge_later.
class ActiveStorage::PurgeJob < ActiveStorage::BaseJob
- # FIXME: Limit this to a custom ActiveStorage error
- retry_on StandardError
+ discard_on ActiveRecord::RecordNotFound
+ retry_on ActiveRecord::Deadlocked, attempts: 10, wait: :exponentially_longer
def perform(blob)
blob.purge
diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb
index c59877a9a5..4bdd1c0224 100644
--- a/activestorage/app/models/active_storage/attachment.rb
+++ b/activestorage/app/models/active_storage/attachment.rb
@@ -15,17 +15,18 @@ class ActiveStorage::Attachment < ActiveRecord::Base
delegate_missing_to :blob
after_create_commit :analyze_blob_later, :identify_blob
+ after_destroy_commit :purge_dependent_blob_later
- # Synchronously purges the blob (deletes it from the configured service) and destroys the attachment.
+ # Synchronously deletes the attachment and {purges the blob}[rdoc-ref:ActiveStorage::Blob#purge].
def purge
- blob.purge
- destroy
+ delete
+ blob&.purge
end
- # Destroys the attachment and asynchronously purges the blob (deletes it from the configured service).
+ # Deletes the attachment and {enqueues a background job}[rdoc-ref:ActiveStorage::Blob#purge_later] to purge the blob.
def purge_later
- blob.purge_later
- destroy
+ delete
+ blob&.purge_later
end
private
@@ -36,4 +37,13 @@ class ActiveStorage::Attachment < ActiveRecord::Base
def analyze_blob_later
blob.analyze_later unless blob.analyzed?
end
+
+ def purge_dependent_blob_later
+ blob&.purge_later if dependent == :purge_later
+ end
+
+
+ def dependent
+ record.attachment_reflections[name]&.options[:dependent]
+ end
end
diff --git a/activestorage/app/models/active_storage/blob.rb b/activestorage/app/models/active_storage/blob.rb
index 0cd4ad8128..e7f2615b0f 100644
--- a/activestorage/app/models/active_storage/blob.rb
+++ b/activestorage/app/models/active_storage/blob.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require "active_storage/downloader"
+
# A blob is a record that contains the metadata about a file and a key for where that file resides on the service.
# Blobs can be created in two ways:
#
@@ -44,21 +46,25 @@ class ActiveStorage::Blob < ActiveRecord::Base
end
# Returns a new, unsaved blob instance after the +io+ has been uploaded to the service.
- def build_after_upload(io:, filename:, content_type: nil, metadata: nil)
- new.tap do |blob|
- blob.filename = filename
- blob.content_type = content_type
- blob.metadata = metadata
+ # When providing a content type, pass <tt>identify: false</tt> to bypass automatic content type inference.
+ def build_after_upload(io:, filename:, content_type: nil, metadata: nil, identify: true)
+ new(filename: filename, content_type: content_type, metadata: metadata).tap do |blob|
+ blob.upload(io, identify: identify)
+ end
+ end
- blob.upload io
+ def build_after_unfurling(io:, filename:, content_type: nil, metadata: nil, identify: true) #:nodoc:
+ new(filename: filename, content_type: content_type, metadata: metadata).tap do |blob|
+ blob.unfurl(io, identify: identify)
end
end
# Returns a saved blob instance after the +io+ has been uploaded to the service. Note, the blob is first built,
# then the +io+ is uploaded, then the blob is saved. This is done this way to avoid uploading (which may take
# time), while having an open database transaction.
- def create_after_upload!(io:, filename:, content_type: nil, metadata: nil)
- build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!)
+ # When providing a content type, pass <tt>identify: false</tt> to bypass automatic content type inference.
+ def create_after_upload!(io:, filename:, content_type: nil, metadata: nil, identify: true)
+ build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata, identify: identify).tap(&:save!)
end
# Returns a saved blob _without_ uploading a file to the service. This blob will point to a key where there is
@@ -117,7 +123,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
# with users. Instead, the +service_url+ should only be exposed as a redirect from a stable, possibly authenticated URL.
# Hiding the +service_url+ behind a redirect also gives you the power to change services without updating all URLs. And
# it allows permanent URLs that redirect to the +service_url+ to be cached in the view.
- def service_url(expires_in: service.url_expires_in, disposition: :inline, filename: nil, **options)
+ def service_url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline, filename: nil, **options)
filename = ActiveStorage::Filename.wrap(filename || self.filename)
service.url key, expires_in: expires_in, filename: filename, content_type: content_type,
@@ -126,7 +132,7 @@ class ActiveStorage::Blob < ActiveRecord::Base
# Returns a URL that can be used to directly upload a file for this blob on the service. This URL is intended to be
# short-lived for security and only generated on-demand by the client-side JavaScript responsible for doing the uploading.
- def service_url_for_direct_upload(expires_in: service.url_expires_in)
+ def service_url_for_direct_upload(expires_in: ActiveStorage.service_urls_expire_in)
service.url_for_direct_upload key, expires_in: expires_in, content_type: content_type, content_length: byte_size, checksum: checksum
end
@@ -142,17 +148,25 @@ class ActiveStorage::Blob < ActiveRecord::Base
#
# Prior to uploading, we compute the checksum, which is sent to the service for transit integrity validation. If the
# checksum does not match what the service receives, an exception will be raised. We also measure the size of the +io+
- # and store that in +byte_size+ on the blob record.
+ # and store that in +byte_size+ on the blob record. The content type is automatically extracted from the +io+ unless
+ # you specify a +content_type+ and pass +identify+ as false.
#
# Normally, you do not have to call this method directly at all. Use the factory class methods of +build_after_upload+
# and +create_after_upload!+.
- def upload(io)
+ def upload(io, identify: true)
+ unfurl io, identify: identify
+ upload_without_unfurling io
+ end
+
+ def unfurl(io, identify: true) #:nodoc:
self.checksum = compute_checksum_in_chunks(io)
- self.content_type = extract_content_type(io)
+ self.content_type = extract_content_type(io) if content_type.nil? || identify
self.byte_size = io.size
self.identified = true
+ end
- service.upload(key, io, checksum: checksum)
+ def upload_without_unfurling(io) #:nodoc:
+ service.upload key, io, checksum: checksum
end
# Downloads the file associated with this blob. If no block is given, the entire file is read into memory and returned.
@@ -161,9 +175,26 @@ class ActiveStorage::Blob < ActiveRecord::Base
service.download key, &block
end
+ # Downloads the blob to a tempfile on disk. Yields the tempfile.
+ #
+ # The tempfile's name is prefixed with +ActiveStorage-+ and the blob's ID. Its extension matches that of the blob.
+ #
+ # By default, the tempfile is created in <tt>Dir.tmpdir</tt>. Pass +tempdir:+ to create it in a different directory:
+ #
+ # blob.open(tempdir: "/path/to/tmp") do |file|
+ # # ...
+ # end
+ #
+ # The tempfile is automatically closed and unlinked after the given block is executed.
+ #
+ # Raises ActiveStorage::IntegrityError if the downloaded data does not match the blob's checksum.
+ def open(tempdir: nil, &block)
+ ActiveStorage::Downloader.new(self, tempdir: tempdir).download_blob_to_tempfile(&block)
+ end
- # Deletes the file on the service that's associated with this blob. This should only be done if the blob is going to be
- # deleted as well or you will essentially have a dead reference. It's recommended to use the +#purge+ and +#purge_later+
+
+ # Deletes the files on the service associated with the blob. This should only be done if the blob is going to be
+ # deleted as well or you will essentially have a dead reference. It's recommended to use #purge and #purge_later
# methods in most circumstances.
def delete
service.delete(key)
@@ -172,14 +203,15 @@ class ActiveStorage::Blob < ActiveRecord::Base
# Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted
# blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may
- # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use +#purge_later+ instead.
+ # be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use #purge_later instead.
def purge
- delete
destroy
+ delete
+ rescue ActiveRecord::InvalidForeignKey
end
- # Enqueues an ActiveStorage::PurgeJob job that'll call +purge+. This is the recommended way to purge blobs when the call
- # needs to be made from a transaction, a callback, or any other real-time scenario.
+ # Enqueues an ActiveStorage::PurgeJob to call #purge. This is the recommended way to purge blobs from a transaction,
+ # an Active Record callback, or in any other real-time scenario.
def purge_later
ActiveStorage::PurgeJob.perform_later(self)
end
diff --git a/activestorage/app/models/active_storage/blob/representable.rb b/activestorage/app/models/active_storage/blob/representable.rb
index fea62e62de..03d5511481 100644
--- a/activestorage/app/models/active_storage/blob/representable.rb
+++ b/activestorage/app/models/active_storage/blob/representable.rb
@@ -10,7 +10,7 @@ module ActiveStorage::Blob::Representable
# Returns an ActiveStorage::Variant instance with the set of +transformations+ provided. This is only relevant for image
# files, and it allows any image to be transformed for size, colors, and the like. Example:
#
- # avatar.variant(resize: "100x100").processed.service_url
+ # avatar.variant(resize_to_fit: [100, 100]).processed.service_url
#
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100px.
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
@@ -18,7 +18,7 @@ module ActiveStorage::Blob::Representable
# Frequently, though, you don't actually want to transform the variant right away. But rather simply refer to a
# specific variant that can be created by a controller on-demand. Like so:
#
- # <%= image_tag Current.user.avatar.variant(resize: "100x100") %>
+ # <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %>
#
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
# can then produce on-demand.
@@ -43,13 +43,13 @@ module ActiveStorage::Blob::Representable
# from a non-image blob. Active Storage comes with built-in previewers for videos and PDF documents. The video previewer
# extracts the first frame from a video and the PDF previewer extracts the first page from a PDF document.
#
- # blob.preview(resize: "100x100").processed.service_url
+ # blob.preview(resize_to_fit: [100, 100]).processed.service_url
#
# Avoid processing previews synchronously in views. Instead, link to a controller action that processes them on demand.
# Active Storage provides one, but you may want to create your own (for example, if you need authentication). Here’s
# how to use the built-in version:
#
- # <%= image_tag video.preview(resize: "100x100") %>
+ # <%= image_tag video.preview(resize_to_fit: [100, 100]) %>
#
# This method raises ActiveStorage::UnpreviewableError if no previewer accepts the receiving blob. To determine
# whether a blob is accepted by any previewer, call ActiveStorage::Blob#previewable?.
@@ -69,7 +69,7 @@ module ActiveStorage::Blob::Representable
# Returns an ActiveStorage::Preview for a previewable blob or an ActiveStorage::Variant for a variable image blob.
#
- # blob.representation(resize: "100x100").processed.service_url
+ # blob.representation(resize_to_fit: [100, 100]).processed.service_url
#
# Raises ActiveStorage::UnrepresentableError if the receiving blob is neither variable nor previewable. Call
# ActiveStorage::Blob#representable? to determine whether a blob is representable.
diff --git a/activestorage/app/models/active_storage/preview.rb b/activestorage/app/models/active_storage/preview.rb
index 2b87897183..dd50494799 100644
--- a/activestorage/app/models/active_storage/preview.rb
+++ b/activestorage/app/models/active_storage/preview.rb
@@ -22,8 +22,8 @@
# Outside of a Rails application, modify +ActiveStorage.previewers+ instead.
#
# The built-in previewers rely on third-party system libraries. Specifically, the built-in video previewer requires
-# {ffmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
-# and the other requires {mupdf}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or mupdf.
+# {FFmpeg}[https://www.ffmpeg.org]. Two PDF previewers are provided: one requires {Poppler}[https://poppler.freedesktop.org],
+# and the other requires {muPDF}[https://mupdf.com] (version 1.8 or newer). To preview PDFs, install either Poppler or muPDF.
#
# These libraries are not provided by Rails. You must install them yourself to use the built-in previewers. Before you
# install and use third-party software, make sure you understand the licensing implications of doing so.
@@ -38,7 +38,7 @@ class ActiveStorage::Preview
# Processes the preview if it has not been processed yet. Returns the receiving Preview instance for convenience:
#
- # blob.preview(resize: "100x100").processed.service_url
+ # blob.preview(resize_to_fit: [100, 100]).processed.service_url
#
# Processing a preview generates an image from its blob and attaches the preview image to the blob. Because the preview
# image is stored with the blob, it is only generated once.
diff --git a/activestorage/app/models/active_storage/variant.rb b/activestorage/app/models/active_storage/variant.rb
index d84208419c..ea57fa5f78 100644
--- a/activestorage/app/models/active_storage/variant.rb
+++ b/activestorage/app/models/active_storage/variant.rb
@@ -1,24 +1,33 @@
# frozen_string_literal: true
-require "active_storage/downloading"
+require "ostruct"
# Image blobs can have variants that are the result of a set of transformations applied to the original.
# These variants are used to create thumbnails, fixed-size avatars, or any other derivative image from the
# original.
#
-# Variants rely on {MiniMagick}[https://github.com/minimagick/minimagick] for the actual transformations
-# of the file, so you must add <tt>gem "mini_magick"</tt> to your Gemfile if you wish to use variants.
+# Variants rely on {ImageProcessing}[https://github.com/janko-m/image_processing] gem for the actual transformations
+# of the file, so you must add <tt>gem "image_processing"</tt> to your Gemfile if you wish to use variants. By
+# default, images will be processed with {ImageMagick}[http://imagemagick.org] using the
+# {MiniMagick}[https://github.com/minimagick/minimagick] gem, but you can also switch to the
+# {libvips}[http://jcupitt.github.io/libvips/] processor operated by the {ruby-vips}[https://github.com/jcupitt/ruby-vips]
+# gem).
#
-# Note that to create a variant it's necessary to download the entire blob file from the service and load it
-# into memory. The larger the image, the more memory is used. Because of this process, you also want to be
-# considerate about when the variant is actually processed. You shouldn't be processing variants inline in a
-# template, for example. Delay the processing to an on-demand controller, like the one provided in
+# Rails.application.config.active_storage.variant_processor
+# # => :mini_magick
+#
+# Rails.application.config.active_storage.variant_processor = :vips
+# # => :vips
+#
+# Note that to create a variant it's necessary to download the entire blob file from the service. Because of this process,
+# you also want to be considerate about when the variant is actually processed. You shouldn't be processing variants inline
+# in a template, for example. Delay the processing to an on-demand controller, like the one provided in
# ActiveStorage::RepresentationsController.
#
# To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
# by Active Storage like so:
#
-# <%= image_tag Current.user.avatar.variant(resize: "100x100") %>
+# <%= image_tag Current.user.avatar.variant(resize_to_fit: [100, 100]) %>
#
# This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
# can then produce on-demand.
@@ -27,19 +36,24 @@ require "active_storage/downloading"
# has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
# the transformations, upload the variant to the service, and return itself again. Example:
#
-# avatar.variant(resize: "100x100").processed.service_url
+# avatar.variant(resize_to_fit: [100, 100]).processed.service_url
#
# This will create and process a variant of the avatar blob that's constrained to a height and width of 100.
# Then it'll upload said variant to the service according to a derivative key of the blob and the transformations.
#
-# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php. You can
-# combine as many as you like freely:
+# You can combine any number of ImageMagick/libvips operations into a variant, as well as any macros provided by the
+# ImageProcessing gem (such as +resize_to_fit+):
+#
+# avatar.variant(resize_to_fit: [800, 800], monochrome: true, rotate: "-90")
+#
+# Visit the following links for a list of available ImageProcessing commands and ImageMagick/libvips operations:
#
-# avatar.variant(resize: "100x100", monochrome: true, flip: "-90")
+# * {ImageProcessing::MiniMagick}[https://github.com/janko-m/image_processing/blob/master/doc/minimagick.md#methods]
+# * {ImageMagick reference}[https://www.imagemagick.org/script/mogrify.php]
+# * {ImageProcessing::Vips}[https://github.com/janko-m/image_processing/blob/master/doc/vips.md#methods]
+# * {ruby-vips reference}[http://www.rubydoc.info/gems/ruby-vips/Vips/Image]
class ActiveStorage::Variant
- include ActiveStorage::Downloading
-
- WEB_IMAGE_CONTENT_TYPES = %w( image/png image/jpeg image/jpg image/gif )
+ WEB_IMAGE_CONTENT_TYPES = %w[ image/png image/jpeg image/jpg image/gif ]
attr_reader :blob, :variation
delegate :service, to: :blob
@@ -67,7 +81,7 @@ class ActiveStorage::Variant
# Use <tt>url_for(variant)</tt> (or the implied form, like +link_to variant+ or +redirect_to variant+) to get the stable URL
# for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
# for its redirection.
- def service_url(expires_in: service.url_expires_in, disposition: :inline)
+ def service_url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
end
@@ -82,51 +96,36 @@ class ActiveStorage::Variant
end
def process
- open_image do |image|
- transform image
- format image
- upload image
+ blob.open do |image|
+ transform(image) { |output| upload(output) }
end
end
-
- def filename
- if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
- blob.filename
- else
- ActiveStorage::Filename.new("#{blob.filename.base}.png")
- end
+ def transform(image, &block)
+ variation.transform(image, format: format, &block)
end
- def content_type
- blob.content_type.presence_in(WEB_IMAGE_CONTENT_TYPES) || "image/png"
+ def upload(file)
+ service.upload(key, file)
end
- def open_image(&block)
- image = download_image
-
- begin
- yield image
- ensure
- image.destroy!
- end
- end
-
- def download_image
- require "mini_magick"
- MiniMagick::Image.create(blob.filename.extension_with_delimiter) { |file| download_blob_to(file) }
+ def specification
+ @specification ||=
+ if WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
+ Specification.new \
+ filename: blob.filename,
+ content_type: blob.content_type,
+ format: nil
+ else
+ Specification.new \
+ filename: ActiveStorage::Filename.new("#{blob.filename.base}.png"),
+ content_type: "image/png",
+ format: "png"
+ end
end
- def transform(image)
- variation.transform(image)
- end
+ delegate :filename, :content_type, :format, to: :specification
- def format(image)
- image.format("PNG") unless WEB_IMAGE_CONTENT_TYPES.include?(blob.content_type)
- end
-
- def upload(image)
- File.open(image.path, "r") { |file| service.upload(key, file) }
- end
+ class Specification < OpenStruct; end
end
diff --git a/activestorage/app/models/active_storage/variation.rb b/activestorage/app/models/active_storage/variation.rb
index 12e7f9f0b5..3adc2407e5 100644
--- a/activestorage/app/models/active_storage/variation.rb
+++ b/activestorage/app/models/active_storage/variation.rb
@@ -6,17 +6,9 @@
# In case you do need to use this directly, it's instantiated using a hash of transformations where
# the key is the command and the value is the arguments. Example:
#
-# ActiveStorage::Variation.new(resize: "100x100", monochrome: true, trim: true, rotate: "-90")
+# ActiveStorage::Variation.new(resize_to_fit: [100, 100], monochrome: true, trim: true, rotate: "-90")
#
-# You can also combine multiple transformations in one step, e.g. for center-weighted cropping:
-#
-# ActiveStorage::Variation.new(combine_options: {
-# resize: "100x100^",
-# gravity: "center",
-# crop: "100x100+0+0",
-# })
-#
-# A list of all possible transformations is available at https://www.imagemagick.org/script/mogrify.php.
+# The options map directly to {ImageProcessing}[https://github.com/janko-m/image_processing] commands.
class ActiveStorage::Variation
attr_reader :transformations
@@ -51,21 +43,13 @@ class ActiveStorage::Variation
@transformations = transformations
end
- # Accepts an open MiniMagick image instance, like what's returned by <tt>MiniMagick::Image.read(io)</tt>,
- # and performs the +transformations+ against it. The transformed image instance is then returned.
- def transform(image)
+ # Accepts a File object, performs the +transformations+ against it, and
+ # saves the transformed image into a temporary file. If +format+ is specified
+ # it will be the format of the result image, otherwise the result image
+ # retains the source format.
+ def transform(file, format: nil, &block)
ActiveSupport::Notifications.instrument("transform.active_storage") do
- transformations.each do |name, argument_or_subtransformations|
- image.mogrify do |command|
- if name.to_s == "combine_options"
- argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
- pass_transform_argument(command, subtransformation_name, subtransformation_argument)
- end
- else
- pass_transform_argument(command, name, argument_or_subtransformations)
- end
- end
- end
+ transformer.transform(file, format: format, &block)
end
end
@@ -75,15 +59,22 @@ class ActiveStorage::Variation
end
private
- def pass_transform_argument(command, method, argument)
- if eligible_argument?(argument)
- command.public_send(method, argument)
+ def transformer
+ if ActiveStorage.variant_processor
+ begin
+ require "image_processing"
+ rescue LoadError
+ ActiveSupport::Deprecation.warn <<~WARNING
+ Generating image variants will require the image_processing gem in Rails 6.1.
+ Please add `gem 'image_processing', '~> 1.2'` to your Gemfile.
+ WARNING
+
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
+ else
+ ActiveStorage::Transformers::ImageProcessingTransformer.new(transformations)
+ end
else
- command.public_send(method)
+ ActiveStorage::Transformers::MiniMagickTransformer.new(transformations)
end
end
-
- def eligible_argument?(argument)
- argument.present? && argument != true
- end
end
diff --git a/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb
index 9e31e3966a..cfaf01cd5e 100644
--- a/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb
+++ b/activestorage/db/migrate/20170806125915_create_active_storage_tables.rb
@@ -20,6 +20,7 @@ class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
t.datetime :created_at, null: false
t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
end
end
end
diff --git a/activestorage/lib/active_storage.rb b/activestorage/lib/active_storage.rb
index e1bd974853..d3e3a2f49b 100644
--- a/activestorage/lib/active_storage.rb
+++ b/activestorage/lib/active_storage.rb
@@ -45,7 +45,17 @@ module ActiveStorage
mattr_accessor :queue
mattr_accessor :previewers, default: []
mattr_accessor :analyzers, default: []
+ mattr_accessor :variant_processor, default: :mini_magick
mattr_accessor :paths, default: {}
mattr_accessor :variable_content_types, default: []
mattr_accessor :content_types_to_serve_as_binary, default: []
+ mattr_accessor :service_urls_expire_in, default: 5.minutes
+
+ module Transformers
+ extend ActiveSupport::Autoload
+
+ autoload :Transformer
+ autoload :ImageProcessingTransformer
+ autoload :MiniMagickTransformer
+ end
end
diff --git a/activestorage/lib/active_storage/analyzer.rb b/activestorage/lib/active_storage/analyzer.rb
index 7c4168c1a0..caa25418a5 100644
--- a/activestorage/lib/active_storage/analyzer.rb
+++ b/activestorage/lib/active_storage/analyzer.rb
@@ -1,13 +1,9 @@
# frozen_string_literal: true
-require "active_storage/downloading"
-
module ActiveStorage
# This is an abstract base class for analyzers, which extract metadata from blobs. See
# ActiveStorage::Analyzer::ImageAnalyzer for an example of a concrete subclass.
class Analyzer
- include Downloading
-
attr_reader :blob
# Implement this method in a concrete subclass. Have it return true when given a blob from which
@@ -26,8 +22,17 @@ module ActiveStorage
end
private
+ # Downloads the blob to a tempfile on disk. Yields the tempfile.
+ def download_blob_to_tempfile(&block) #:doc:
+ blob.open tempdir: tempdir, &block
+ end
+
def logger #:doc:
ActiveStorage.logger
end
+
+ def tempdir #:doc:
+ Dir.tmpdir
+ end
end
end
diff --git a/activestorage/lib/active_storage/analyzer/video_analyzer.rb b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
index e31bdb0edb..18d8ff8237 100644
--- a/activestorage/lib/active_storage/analyzer/video_analyzer.rb
+++ b/activestorage/lib/active_storage/analyzer/video_analyzer.rb
@@ -16,7 +16,7 @@ module ActiveStorage
#
# When a video's angle is 90 or 270 degrees, its width and height are automatically swapped for convenience.
#
- # This analyzer requires the {ffmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
+ # This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by Rails.
class Analyzer::VideoAnalyzer < Analyzer
def self.accept?(blob)
blob.video?
@@ -107,7 +107,7 @@ module ActiveStorage
JSON.parse(output.read)
end
rescue Errno::ENOENT
- logger.info "Skipping video analysis because ffmpeg isn't installed"
+ logger.info "Skipping video analysis because FFmpeg isn't installed"
{}
end
diff --git a/activestorage/lib/active_storage/attached.rb b/activestorage/lib/active_storage/attached.rb
index c08fd56652..b540f85fbe 100644
--- a/activestorage/lib/active_storage/attached.rb
+++ b/activestorage/lib/active_storage/attached.rb
@@ -1,40 +1,25 @@
# frozen_string_literal: true
-require "action_dispatch"
-require "action_dispatch/http/upload"
require "active_support/core_ext/module/delegation"
module ActiveStorage
# Abstract base class for the concrete ActiveStorage::Attached::One and ActiveStorage::Attached::Many
# classes that both provide proxy access to the blob association for a record.
class Attached
- attr_reader :name, :record, :dependent
+ attr_reader :name, :record
- def initialize(name, record, dependent:)
- @name, @record, @dependent = name, record, dependent
+ def initialize(name, record)
+ @name, @record = name, record
end
private
- def create_blob_from(attachable)
- case attachable
- when ActiveStorage::Blob
- attachable
- when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
- ActiveStorage::Blob.create_after_upload! \
- io: attachable.open,
- filename: attachable.original_filename,
- content_type: attachable.content_type
- when Hash
- ActiveStorage::Blob.create_after_upload!(attachable)
- when String
- ActiveStorage::Blob.find_signed(attachable)
- else
- nil
- end
+ def change
+ record.attachment_changes[name]
end
end
end
+require "active_storage/attached/model"
require "active_storage/attached/one"
require "active_storage/attached/many"
-require "active_storage/attached/macros"
+require "active_storage/attached/changes"
diff --git a/activestorage/lib/active_storage/attached/changes.rb b/activestorage/lib/active_storage/attached/changes.rb
new file mode 100644
index 0000000000..1db3906a63
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ module Attached::Changes #:nodoc:
+ extend ActiveSupport::Autoload
+
+ eager_autoload do
+ autoload :CreateOne
+ autoload :CreateMany
+ autoload :CreateOneOfMany
+
+ autoload :DeleteOne
+ autoload :DeleteMany
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/create_many.rb b/activestorage/lib/active_storage/attached/changes/create_many.rb
new file mode 100644
index 0000000000..a7a8553e0f
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/create_many.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::CreateMany #:nodoc:
+ attr_reader :name, :record, :attachables
+
+ def initialize(name, record, attachables)
+ @name, @record, @attachables = name, record, Array(attachables)
+ end
+
+ def attachments
+ @attachments ||= subchanges.collect(&:attachment)
+ end
+
+ def blobs
+ @blobs ||= subchanges.collect(&:blob)
+ end
+
+ def upload
+ subchanges.each(&:upload)
+ end
+
+ def save
+ assign_associated_attachments
+ reset_associated_blobs
+ end
+
+ private
+ def subchanges
+ @subchanges ||= attachables.collect { |attachable| build_subchange_from(attachable) }
+ end
+
+ def build_subchange_from(attachable)
+ ActiveStorage::Attached::Changes::CreateOneOfMany.new(name, record, attachable)
+ end
+
+
+ def assign_associated_attachments
+ record.public_send("#{name}_attachments=", attachments)
+ end
+
+ def reset_associated_blobs
+ record.public_send("#{name}_blobs").reset
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/create_one.rb b/activestorage/lib/active_storage/attached/changes/create_one.rb
new file mode 100644
index 0000000000..5812fd2b08
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/create_one.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require "action_dispatch"
+require "action_dispatch/http/upload"
+
+module ActiveStorage
+ class Attached::Changes::CreateOne #:nodoc:
+ attr_reader :name, :record, :attachable
+
+ def initialize(name, record, attachable)
+ @name, @record, @attachable = name, record, attachable
+ end
+
+ def attachment
+ @attachment ||= find_or_build_attachment
+ end
+
+ def blob
+ @blob ||= find_or_build_blob
+ end
+
+ def upload
+ case attachable
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
+ blob.upload_without_unfurling(attachable.open)
+ when Hash
+ blob.upload_without_unfurling(attachable.fetch(:io))
+ end
+ end
+
+ def save
+ record.public_send("#{name}_attachment=", attachment)
+ end
+
+ private
+ def find_or_build_attachment
+ find_attachment || build_attachment
+ end
+
+ def find_attachment
+ if record.public_send("#{name}_blob") == blob
+ record.public_send("#{name}_attachment")
+ end
+ end
+
+ def build_attachment
+ ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
+ end
+
+ def find_or_build_blob
+ case attachable
+ when ActiveStorage::Blob
+ attachable
+ when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
+ ActiveStorage::Blob.build_after_unfurling \
+ io: attachable.open,
+ filename: attachable.original_filename,
+ content_type: attachable.content_type
+ when Hash
+ ActiveStorage::Blob.build_after_unfurling(attachable)
+ when String
+ ActiveStorage::Blob.find_signed(attachable)
+ else
+ raise ArgumentError, "Could not find or build blob: expected attachable, got #{attachable.inspect}"
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/create_one_of_many.rb b/activestorage/lib/active_storage/attached/changes/create_one_of_many.rb
new file mode 100644
index 0000000000..7268e87316
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/create_one_of_many.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::CreateOneOfMany < Attached::Changes::CreateOne #:nodoc:
+ private
+ def find_attachment
+ record.public_send("#{name}_attachments").detect { |attachment| attachment.blob_id == blob.id }
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/delete_many.rb b/activestorage/lib/active_storage/attached/changes/delete_many.rb
new file mode 100644
index 0000000000..6cbd1158dc
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/delete_many.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::DeleteMany #:nodoc:
+ attr_reader :name, :record
+
+ def initialize(name, record)
+ @name, @record = name, record
+ end
+
+ def attachments
+ ActiveStorage::Attachment.none
+ end
+
+ def blobs
+ ActiveStorage::Blob.none
+ end
+
+ def save
+ record.public_send("#{name}_attachments=", [])
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/changes/delete_one.rb b/activestorage/lib/active_storage/attached/changes/delete_one.rb
new file mode 100644
index 0000000000..2f7d356613
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/changes/delete_one.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Attached::Changes::DeleteOne #:nodoc:
+ attr_reader :name, :record
+
+ def initialize(name, record)
+ @name, @record = name, record
+ end
+
+ def attachment
+ nil
+ end
+
+ def save
+ record.public_send("#{name}_attachment=", nil)
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/macros.rb b/activestorage/lib/active_storage/attached/macros.rb
deleted file mode 100644
index 819f00cc06..0000000000
--- a/activestorage/lib/active_storage/attached/macros.rb
+++ /dev/null
@@ -1,110 +0,0 @@
-# frozen_string_literal: true
-
-module ActiveStorage
- # Provides the class-level DSL for declaring that an Active Record model has attached blobs.
- module Attached::Macros
- # Specifies the relation between a single attachment and the model.
- #
- # class User < ActiveRecord::Base
- # has_one_attached :avatar
- # end
- #
- # There is no column defined on the model side, Active Storage takes
- # care of the mapping between your records and the attachment.
- #
- # To avoid N+1 queries, you can include the attached blobs in your query like so:
- #
- # User.with_attached_avatar
- #
- # Under the covers, this relationship is implemented as a +has_one+ association to a
- # ActiveStorage::Attachment record and a +has_one-through+ association to a
- # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
- # and +avatar_blob+. But you shouldn't need to work with these associations directly in
- # most circumstances.
- #
- # The system has been designed to having you go through the ActiveStorage::Attached::One
- # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
- #
- # If the +:dependent+ option isn't set, the attachment will be purged
- # (i.e. destroyed) whenever the record is destroyed.
- def has_one_attached(name, dependent: :purge_later)
- class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}
- @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
- end
-
- def #{name}=(attachable)
- #{name}.attach(attachable)
- end
- CODE
-
- has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: false
- has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
-
- scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
-
- if dependent == :purge_later
- after_destroy_commit { public_send(name).purge_later }
- else
- before_destroy { public_send(name).detach }
- end
- end
-
- # Specifies the relation between multiple attachments and the model.
- #
- # class Gallery < ActiveRecord::Base
- # has_many_attached :photos
- # end
- #
- # There are no columns defined on the model side, Active Storage takes
- # care of the mapping between your records and the attachments.
- #
- # To avoid N+1 queries, you can include the attached blobs in your query like so:
- #
- # Gallery.where(user: Current.user).with_attached_photos
- #
- # Under the covers, this relationship is implemented as a +has_many+ association to a
- # ActiveStorage::Attachment record and a +has_many-through+ association to a
- # ActiveStorage::Blob record. These associations are available as +photos_attachments+
- # and +photos_blobs+. But you shouldn't need to work with these associations directly in
- # most circumstances.
- #
- # The system has been designed to having you go through the ActiveStorage::Attached::Many
- # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
- #
- # If the +:dependent+ option isn't set, all the attachments will be purged
- # (i.e. destroyed) whenever the record is destroyed.
- def has_many_attached(name, dependent: :purge_later)
- class_eval <<-CODE, __FILE__, __LINE__ + 1
- def #{name}
- @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self, dependent: #{dependent == :purge_later ? ":purge_later" : "false"})
- end
-
- def #{name}=(attachables)
- #{name}.attach(attachables)
- end
- CODE
-
- has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: false do
- def purge
- each(&:purge)
- reset
- end
-
- def purge_later
- each(&:purge_later)
- reset
- end
- end
- has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
-
- scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
-
- if dependent == :purge_later
- after_destroy_commit { public_send(name).purge_later }
- else
- before_destroy { public_send(name).detach }
- end
- end
- end
-end
diff --git a/activestorage/lib/active_storage/attached/many.rb b/activestorage/lib/active_storage/attached/many.rb
index d61acb6fad..25f88284df 100644
--- a/activestorage/lib/active_storage/attached/many.rb
+++ b/activestorage/lib/active_storage/attached/many.rb
@@ -9,22 +9,29 @@ module ActiveStorage
#
# All methods called on this proxy object that aren't listed here will automatically be delegated to +attachments+.
def attachments
- record.public_send("#{name}_attachments")
+ change.present? ? change.attachments : record.public_send("#{name}_attachments")
end
- # Associates one or several attachments with the current record, saving them to the database.
+ # Returns all attached blobs.
+ def blobs
+ change.present? ? change.blobs : record.public_send("#{name}_blobs")
+ end
+
+ # Attaches one or more +attachables+ to the record.
+ #
+ # If the record is persisted and unchanged, the attachments are saved to
+ # the database immediately. Otherwise, they'll be saved to the DB when the
+ # record is next saved.
#
# document.images.attach(params[:images]) # Array of ActionDispatch::Http::UploadedFile objects
# document.images.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
# document.images.attach(io: File.open("/path/to/racecar.jpg"), filename: "racecar.jpg", content_type: "image/jpg")
# document.images.attach([ first_blob, second_blob ])
def attach(*attachables)
- attachables.flatten.collect do |attachable|
- if record.new_record?
- attachments.build(record: record, blob: create_blob_from(attachable))
- else
- attachments.create!(record: record, blob: create_blob_from(attachable))
- end
+ if record.persisted? && !record.changed?
+ record.update(name => blobs + attachables.flatten)
+ else
+ record.public_send("#{name}=", blobs + attachables.flatten)
end
end
@@ -41,7 +48,7 @@ module ActiveStorage
# Deletes associated attachments without purging them, leaving their respective blobs in place.
def detach
- attachments.destroy_all if attached?
+ attachments.delete_all if attached?
end
##
@@ -50,7 +57,6 @@ module ActiveStorage
# Directly purges each associated attachment (i.e. destroys the blobs and
# attachments and deletes the files on the service).
-
##
# :method: purge_later
#
diff --git a/activestorage/lib/active_storage/attached/model.rb b/activestorage/lib/active_storage/attached/model.rb
new file mode 100644
index 0000000000..ae7f0685f2
--- /dev/null
+++ b/activestorage/lib/active_storage/attached/model.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ # Provides the class-level DSL for declaring an Active Record model's attachments.
+ module Attached::Model
+ extend ActiveSupport::Concern
+
+ class_methods do
+ # Specifies the relation between a single attachment and the model.
+ #
+ # class User < ActiveRecord::Base
+ # has_one_attached :avatar
+ # end
+ #
+ # There is no column defined on the model side, Active Storage takes
+ # care of the mapping between your records and the attachment.
+ #
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
+ #
+ # User.with_attached_avatar
+ #
+ # Under the covers, this relationship is implemented as a +has_one+ association to a
+ # ActiveStorage::Attachment record and a +has_one-through+ association to a
+ # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
+ # and +avatar_blob+. But you shouldn't need to work with these associations directly in
+ # most circumstances.
+ #
+ # The system has been designed to having you go through the ActiveStorage::Attached::One
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
+ #
+ # If the +:dependent+ option isn't set, the attachment will be purged
+ # (i.e. destroyed) whenever the record is destroyed.
+ def has_one_attached(name, dependent: :purge_later)
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}
+ @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self)
+ end
+
+ def #{name}=(attachable)
+ attachment_changes["#{name}"] =
+ if attachable.nil?
+ ActiveStorage::Attached::Changes::DeleteOne.new("#{name}", self)
+ else
+ ActiveStorage::Attached::Changes::CreateOne.new("#{name}", self, attachable)
+ end
+ end
+ CODE
+
+ has_one :"#{name}_attachment", -> { where(name: name) }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: :destroy
+ has_one :"#{name}_blob", through: :"#{name}_attachment", class_name: "ActiveStorage::Blob", source: :blob
+
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachment": :blob) }
+
+ after_save { attachment_changes[name.to_s]&.save }
+
+ after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
+
+ ActiveRecord::Reflection.add_attachment_reflection(
+ self,
+ name,
+ ActiveRecord::Reflection.create(:has_one_attached, name, nil, { dependent: dependent }, self)
+ )
+ end
+
+ # Specifies the relation between multiple attachments and the model.
+ #
+ # class Gallery < ActiveRecord::Base
+ # has_many_attached :photos
+ # end
+ #
+ # There are no columns defined on the model side, Active Storage takes
+ # care of the mapping between your records and the attachments.
+ #
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
+ #
+ # Gallery.where(user: Current.user).with_attached_photos
+ #
+ # Under the covers, this relationship is implemented as a +has_many+ association to a
+ # ActiveStorage::Attachment record and a +has_many-through+ association to a
+ # ActiveStorage::Blob record. These associations are available as +photos_attachments+
+ # and +photos_blobs+. But you shouldn't need to work with these associations directly in
+ # most circumstances.
+ #
+ # The system has been designed to having you go through the ActiveStorage::Attached::Many
+ # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
+ #
+ # If the +:dependent+ option isn't set, all the attachments will be purged
+ # (i.e. destroyed) whenever the record is destroyed.
+ def has_many_attached(name, dependent: :purge_later)
+ generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def #{name}
+ @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self)
+ end
+
+ def #{name}=(attachables)
+ attachment_changes["#{name}"] =
+ if attachables.nil? || Array(attachables).none?
+ ActiveStorage::Attached::Changes::DeleteMany.new("#{name}", self)
+ else
+ ActiveStorage::Attached::Changes::CreateMany.new("#{name}", self, attachables)
+ end
+ end
+ CODE
+
+ has_many :"#{name}_attachments", -> { where(name: name) }, as: :record, class_name: "ActiveStorage::Attachment", inverse_of: :record, dependent: :destroy do
+ def purge
+ each(&:purge)
+ reset
+ end
+
+ def purge_later
+ each(&:purge_later)
+ reset
+ end
+ end
+ has_many :"#{name}_blobs", through: :"#{name}_attachments", class_name: "ActiveStorage::Blob", source: :blob
+
+ scope :"with_attached_#{name}", -> { includes("#{name}_attachments": :blob) }
+
+ after_save { attachment_changes[name.to_s]&.save }
+
+ after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
+
+ ActiveRecord::Reflection.add_attachment_reflection(
+ self,
+ name,
+ ActiveRecord::Reflection.create(:has_many_attached, name, nil, { dependent: dependent }, self)
+ )
+ end
+ end
+
+ def attachment_changes #:nodoc:
+ @attachment_changes ||= {}
+ end
+
+ def reload(*) #:nodoc:
+ super.tap { @attachment_changes = nil }
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/attached/one.rb b/activestorage/lib/active_storage/attached/one.rb
index a582ed0137..c039226fcd 100644
--- a/activestorage/lib/active_storage/attached/one.rb
+++ b/activestorage/lib/active_storage/attached/one.rb
@@ -10,26 +10,28 @@ module ActiveStorage
# You don't have to call this method to access the attachment's methods as
# they are all available at the model level.
def attachment
- record.public_send("#{name}_attachment")
+ change.present? ? change.attachment : record.public_send("#{name}_attachment")
end
- # Associates a given attachment with the current record, saving it to the database.
+ def blank?
+ !attached?
+ end
+
+ # Attaches an +attachable+ to the record.
+ #
+ # If the record is persisted and unchanged, the attachment is saved to
+ # the database immediately. Otherwise, it'll be saved to the DB when the
+ # record is next saved.
#
# person.avatar.attach(params[:avatar]) # ActionDispatch::Http::UploadedFile object
# person.avatar.attach(params[:signed_blob_id]) # Signed reference to blob from direct upload
# person.avatar.attach(io: File.open("/path/to/face.jpg"), filename: "face.jpg", content_type: "image/jpg")
# person.avatar.attach(avatar_blob) # ActiveStorage::Blob object
def attach(attachable)
- blob_was = blob if attached?
- blob = create_blob_from(attachable)
-
- unless blob == blob_was
- transaction do
- detach
- write_attachment build_attachment(blob: blob)
- end
-
- blob_was.purge_later if blob_was && dependent == :purge_later
+ if record.persisted? && !record.changed?
+ record.update(name => attachable)
+ else
+ record.public_send("#{name}=", attachable)
end
end
@@ -47,7 +49,7 @@ module ActiveStorage
# Deletes the attachment without purging it, leaving its blob in place.
def detach
if attached?
- attachment.destroy
+ attachment.delete
write_attachment nil
end
end
@@ -65,16 +67,11 @@ module ActiveStorage
def purge_later
if attached?
attachment.purge_later
+ write_attachment nil
end
end
private
- delegate :transaction, to: :record
-
- def build_attachment(blob:)
- ActiveStorage::Attachment.new(record: record, name: name, blob: blob)
- end
-
def write_attachment(attachment)
record.public_send("#{name}_attachment=", attachment)
end
diff --git a/activestorage/lib/active_storage/downloader.rb b/activestorage/lib/active_storage/downloader.rb
new file mode 100644
index 0000000000..87be6efb05
--- /dev/null
+++ b/activestorage/lib/active_storage/downloader.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ class Downloader #:nodoc:
+ def initialize(blob, tempdir: nil)
+ @blob = blob
+ @tempdir = tempdir
+ end
+
+ def download_blob_to_tempfile
+ open_tempfile do |file|
+ download_blob_to file
+ verify_integrity_of file
+ yield file
+ end
+ end
+
+ private
+ attr_reader :blob, :tempdir
+
+ def open_tempfile
+ file = Tempfile.open([ "ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter ], tempdir)
+
+ begin
+ yield file
+ ensure
+ file.close!
+ end
+ end
+
+ def download_blob_to(file)
+ file.binmode
+ blob.download { |chunk| file.write(chunk) }
+ file.flush
+ file.rewind
+ end
+
+ def verify_integrity_of(file)
+ unless Digest::MD5.file(file).base64digest == blob.checksum
+ raise ActiveStorage::IntegrityError
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/downloading.rb b/activestorage/lib/active_storage/downloading.rb
index f2a1fffdcb..df820bc088 100644
--- a/activestorage/lib/active_storage/downloading.rb
+++ b/activestorage/lib/active_storage/downloading.rb
@@ -1,9 +1,17 @@
# frozen_string_literal: true
require "tmpdir"
+require "active_support/core_ext/string/filters"
module ActiveStorage
module Downloading
+ def self.included(klass)
+ ActiveSupport::Deprecation.warn <<~MESSAGE.squish, caller_locations(2)
+ ActiveStorage::Downloading is deprecated and will be removed in Active Storage 6.1.
+ Use ActiveStorage::Blob#open instead.
+ MESSAGE
+ end
+
private
# Opens a new tempfile in #tempdir and copies blob data into it. Yields the tempfile.
def download_blob_to_tempfile #:doc:
@@ -27,6 +35,7 @@ module ActiveStorage
def download_blob_to(file) #:doc:
file.binmode
blob.download { |chunk| file.write(chunk) }
+ file.flush
file.rewind
end
diff --git a/activestorage/lib/active_storage/engine.rb b/activestorage/lib/active_storage/engine.rb
index 1385e2aa84..9d6a27eabe 100644
--- a/activestorage/lib/active_storage/engine.rb
+++ b/activestorage/lib/active_storage/engine.rb
@@ -10,6 +10,8 @@ require "active_storage/previewer/video_previewer"
require "active_storage/analyzer/image_analyzer"
require "active_storage/analyzer/video_analyzer"
+require "active_storage/reflection"
+
module ActiveStorage
class Engine < Rails::Engine # :nodoc:
isolate_namespace ActiveStorage
@@ -43,14 +45,16 @@ module ActiveStorage
initializer "active_storage.configs" do
config.after_initialize do |app|
- ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
- ActiveStorage.queue = app.config.active_storage.queue
- ActiveStorage.previewers = app.config.active_storage.previewers || []
- ActiveStorage.analyzers = app.config.active_storage.analyzers || []
- ActiveStorage.paths = app.config.active_storage.paths || {}
+ ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
+ ActiveStorage.queue = app.config.active_storage.queue
+ ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
+ ActiveStorage.previewers = app.config.active_storage.previewers || []
+ ActiveStorage.analyzers = app.config.active_storage.analyzers || []
+ ActiveStorage.paths = app.config.active_storage.paths || {}
ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
+ ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
end
end
@@ -58,7 +62,7 @@ module ActiveStorage
require "active_storage/attached"
ActiveSupport.on_load(:active_record) do
- extend ActiveStorage::Attached::Macros
+ include ActiveStorage::Attached::Model
end
end
@@ -94,5 +98,12 @@ module ActiveStorage
end
end
end
+
+ initializer "active_storage.reflection" do
+ ActiveSupport.on_load(:active_record) do
+ include Reflection::ActiveRecordExtensions
+ ActiveRecord::Reflection.singleton_class.prepend(Reflection::ReflectionExtension)
+ end
+ end
end
end
diff --git a/activestorage/lib/active_storage/errors.rb b/activestorage/lib/active_storage/errors.rb
index f099b13f5b..6475c1d076 100644
--- a/activestorage/lib/active_storage/errors.rb
+++ b/activestorage/lib/active_storage/errors.rb
@@ -1,7 +1,26 @@
# frozen_string_literal: true
module ActiveStorage
- class InvariableError < StandardError; end
- class UnpreviewableError < StandardError; end
- class UnrepresentableError < StandardError; end
+ # Generic base class for all Active Storage exceptions.
+ class Error < StandardError; end
+
+ # Raised when ActiveStorage::Blob#variant is called on a blob that isn't variable.
+ # Use ActiveStorage::Blob#variable? to determine whether a blob is variable.
+ class InvariableError < Error; end
+
+ # Raised when ActiveStorage::Blob#preview is called on a blob that isn't previewable.
+ # Use ActiveStorage::Blob#previewable? to determine whether a blob is previewable.
+ class UnpreviewableError < Error; end
+
+ # Raised when ActiveStorage::Blob#representation is called on a blob that isn't representable.
+ # Use ActiveStorage::Blob#representable? to determine whether a blob is representable.
+ class UnrepresentableError < Error; end
+
+ # Raised when uploaded or downloaded data does not match a precomputed checksum.
+ # Indicates that a network error or a software bug caused data corruption.
+ class IntegrityError < Error; end
+
+ # Raised when ActiveStorage::Blob#download is called on a blob where the
+ # backing file is no longer present in its service.
+ class FileNotFoundError < Error; end
end
diff --git a/activestorage/lib/active_storage/log_subscriber.rb b/activestorage/lib/active_storage/log_subscriber.rb
index a4e148c1a5..6c0b4c30e7 100644
--- a/activestorage/lib/active_storage/log_subscriber.rb
+++ b/activestorage/lib/active_storage/log_subscriber.rb
@@ -14,6 +14,8 @@ module ActiveStorage
info event, color("Downloaded file from key: #{key_in(event)}", BLUE)
end
+ alias_method :service_streaming_download, :service_download
+
def service_delete(event)
info event, color("Deleted file from key: #{key_in(event)}", RED)
end
diff --git a/activestorage/lib/active_storage/previewer.rb b/activestorage/lib/active_storage/previewer.rb
index cf19987d72..95a041fd16 100644
--- a/activestorage/lib/active_storage/previewer.rb
+++ b/activestorage/lib/active_storage/previewer.rb
@@ -1,14 +1,10 @@
# frozen_string_literal: true
-require "active_storage/downloading"
-
module ActiveStorage
# This is an abstract base class for previewers, which generate images from blobs. See
- # ActiveStorage::Previewer::PDFPreviewer and ActiveStorage::Previewer::VideoPreviewer for examples of
- # concrete subclasses.
+ # ActiveStorage::Previewer::MuPDFPreviewer and ActiveStorage::Previewer::VideoPreviewer for
+ # examples of concrete subclasses.
class Previewer
- include Downloading
-
attr_reader :blob
# Implement this method in a concrete subclass. Have it return true when given a blob from which
@@ -28,9 +24,14 @@ module ActiveStorage
end
private
+ # Downloads the blob to a tempfile on disk. Yields the tempfile.
+ def download_blob_to_tempfile(&block) #:doc:
+ blob.open tempdir: tempdir, &block
+ end
+
# Executes a system command, capturing its binary output in a tempfile. Yields the tempfile.
#
- # Use this method to shell out to a system library (e.g. mupdf or ffmpeg) for preview image
+ # Use this method to shell out to a system library (e.g. muPDF or FFmpeg) for preview image
# generation. The resulting tempfile can be used as the +:io+ value in an attachable Hash:
#
# def preview
@@ -41,18 +42,19 @@ module ActiveStorage
# end
# end
#
- # The output tempfile is opened in the directory returned by ActiveStorage::Downloading#tempdir.
+ # The output tempfile is opened in the directory returned by #tempdir.
def draw(*argv) #:doc:
- ActiveSupport::Notifications.instrument("preview.active_storage") do
- open_tempfile_for_drawing do |file|
+ open_tempfile do |file|
+ instrument :preview, key: blob.key do
capture(*argv, to: file)
- yield file
end
+
+ yield file
end
end
- def open_tempfile_for_drawing
- tempfile = Tempfile.open("ActiveStorage", tempdir)
+ def open_tempfile
+ tempfile = Tempfile.open("ActiveStorage-", tempdir)
begin
yield tempfile
@@ -61,6 +63,10 @@ module ActiveStorage
end
end
+ def instrument(operation, payload = {}, &block)
+ ActiveSupport::Notifications.instrument "#{operation}.active_storage", payload, &block
+ end
+
def capture(*argv, to:)
to.binmode
IO.popen(argv, err: File::NULL) { |out| IO.copy_stream(out, to) }
@@ -70,5 +76,9 @@ module ActiveStorage
def logger #:doc:
ActiveStorage.logger
end
+
+ def tempdir #:doc:
+ Dir.tmpdir
+ end
end
end
diff --git a/activestorage/lib/active_storage/previewer/poppler_pdf_previewer.rb b/activestorage/lib/active_storage/previewer/poppler_pdf_previewer.rb
index 2a787362cf..69eb617d7b 100644
--- a/activestorage/lib/active_storage/previewer/poppler_pdf_previewer.rb
+++ b/activestorage/lib/active_storage/previewer/poppler_pdf_previewer.rb
@@ -12,7 +12,7 @@ module ActiveStorage
end
def pdftoppm_exists?
- return @pdftoppm_exists unless @pdftoppm_exists.nil?
+ return @pdftoppm_exists if defined?(@pdftoppm_exists)
@pdftoppm_exists = system(pdftoppm_path, "-v", out: File::NULL, err: File::NULL)
end
diff --git a/activestorage/lib/active_storage/previewer/video_previewer.rb b/activestorage/lib/active_storage/previewer/video_previewer.rb
index 2f28a3d341..50e13d202a 100644
--- a/activestorage/lib/active_storage/previewer/video_previewer.rb
+++ b/activestorage/lib/active_storage/previewer/video_previewer.rb
@@ -9,15 +9,14 @@ module ActiveStorage
def preview
download_blob_to_tempfile do |input|
draw_relevant_frame_from input do |output|
- yield io: output, filename: "#{blob.filename.base}.png", content_type: "image/png"
+ yield io: output, filename: "#{blob.filename.base}.jpg", content_type: "image/jpeg"
end
end
end
private
def draw_relevant_frame_from(file, &block)
- draw ffmpeg_path, "-i", file.path, "-y", "-vcodec", "png",
- "-vf", "thumbnail", "-vframes", "1", "-f", "image2", "-", &block
+ draw ffmpeg_path, "-i", file.path, "-y", "-vframes", "1", "-f", "image2", "-", &block
end
def ffmpeg_path
diff --git a/activestorage/lib/active_storage/reflection.rb b/activestorage/lib/active_storage/reflection.rb
new file mode 100644
index 0000000000..ce248c88b5
--- /dev/null
+++ b/activestorage/lib/active_storage/reflection.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ module Reflection
+ # Holds all the metadata about a has_one_attached attachment as it was
+ # specified in the Active Record class.
+ class HasOneAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
+ def macro
+ :has_one_attached
+ end
+ end
+
+ # Holds all the metadata about a has_many_attached attachment as it was
+ # specified in the Active Record class.
+ class HasManyAttachedReflection < ActiveRecord::Reflection::MacroReflection #:nodoc:
+ def macro
+ :has_many_attached
+ end
+ end
+
+ module ReflectionExtension # :nodoc:
+ def add_attachment_reflection(model, name, reflection)
+ model.attachment_reflections = model.attachment_reflections.merge(name.to_s => reflection)
+ end
+
+ private
+ def reflection_class_for(macro)
+ case macro
+ when :has_one_attached
+ HasOneAttachedReflection
+ when :has_many_attached
+ HasManyAttachedReflection
+ else
+ super
+ end
+ end
+ end
+
+ module ActiveRecordExtensions
+ extend ActiveSupport::Concern
+
+ included do
+ class_attribute :attachment_reflections, instance_writer: false, default: {}
+ end
+
+ module ClassMethods
+ # Returns an array of reflection objects for all the attachments in the
+ # class.
+ def reflect_on_all_attachments
+ attachment_reflections.values
+ end
+
+ # Returns the reflection object for the named +attachment+.
+ #
+ # User.reflect_on_attachment(:avatar)
+ # # => the avatar reflection
+ #
+ def reflect_on_attachment(attachment)
+ attachment_reflections[attachment.to_s]
+ end
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/service.rb b/activestorage/lib/active_storage/service.rb
index 949969fc95..f915518f52 100644
--- a/activestorage/lib/active_storage/service.rb
+++ b/activestorage/lib/active_storage/service.rb
@@ -3,8 +3,6 @@
require "active_storage/log_subscriber"
module ActiveStorage
- class IntegrityError < StandardError; end
-
# Abstract class serving as an interface for concrete services.
#
# The available services are:
@@ -41,8 +39,6 @@ module ActiveStorage
extend ActiveSupport::Autoload
autoload :Configurator
- class_attribute :url_expires_in, default: 5.minutes
-
class << self
# Configure an Active Storage service by name from a set of configurations,
# typically loaded from a YAML file. The Active Storage engine uses this
@@ -94,7 +90,7 @@ module ActiveStorage
end
# Returns a signed, temporary URL for the file at the +key+. The URL will be valid for the amount
- # of seconds specified in +expires_in+. You most also provide the +disposition+ (+:inline+ or +:attachment+),
+ # of seconds specified in +expires_in+. You must also provide the +disposition+ (+:inline+ or +:attachment+),
# +filename+, and +content_type+ that you wish the file to be served with on request.
def url(key, expires_in:, disposition:, filename:, content_type:)
raise NotImplementedError
diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb
index 2867a4e441..8de3889cb5 100644
--- a/activestorage/lib/active_storage/service/azure_storage_service.rb
+++ b/activestorage/lib/active_storage/service/azure_storage_service.rb
@@ -19,10 +19,8 @@ module ActiveStorage
def upload(key, io, checksum: nil)
instrument :upload, key: key, checksum: checksum do
- begin
- blobs.create_block_blob(container, key, io, content_md5: checksum)
- rescue Azure::Core::Http::HTTPError
- raise ActiveStorage::IntegrityError
+ handle_errors do
+ blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum)
end
end
end
@@ -34,16 +32,20 @@ module ActiveStorage
end
else
instrument :download, key: key do
- _, io = blobs.get_blob(container, key)
- io.force_encoding(Encoding::BINARY)
+ handle_errors do
+ _, io = blobs.get_blob(container, key)
+ io.force_encoding(Encoding::BINARY)
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
- io.force_encoding(Encoding::BINARY)
+ handle_errors do
+ _, io = blobs.get_blob(container, key, start_range: range.begin, end_range: range.exclude_end? ? range.end - 1 : range.end)
+ io.force_encoding(Encoding::BINARY)
+ end
end
end
@@ -51,7 +53,8 @@ module ActiveStorage
instrument :delete, key: key do
begin
blobs.delete_blob(container, key)
- rescue Azure::Core::Http::HTTPError
+ rescue Azure::Core::Http::HTTPError => e
+ raise unless e.type == "BlobNotFound"
# Ignore files already deleted
end
end
@@ -139,11 +142,26 @@ module ActiveStorage
chunk_size = 5.megabytes
offset = 0
+ raise ActiveStorage::FileNotFoundError unless blob.present?
+
while offset < blob.properties[:content_length]
_, chunk = blobs.get_blob(container, key, start_range: offset, end_range: offset + chunk_size - 1)
yield chunk.force_encoding(Encoding::BINARY)
offset += chunk_size
end
end
+
+ def handle_errors
+ yield
+ rescue Azure::Core::Http::HTTPError => e
+ case e.type
+ when "BlobNotFound"
+ raise ActiveStorage::FileNotFoundError
+ when "Md5Mismatch"
+ raise ActiveStorage::IntegrityError
+ else
+ raise
+ end
+ end
end
end
diff --git a/activestorage/lib/active_storage/service/configurator.rb b/activestorage/lib/active_storage/service/configurator.rb
index 39951fd026..fa80c66c3b 100644
--- a/activestorage/lib/active_storage/service/configurator.rb
+++ b/activestorage/lib/active_storage/service/configurator.rb
@@ -26,7 +26,9 @@ module ActiveStorage
def resolve(class_name)
require "active_storage/service/#{class_name.to_s.underscore}_service"
- ActiveStorage::Service.const_get(:"#{class_name}Service")
+ ActiveStorage::Service.const_get(:"#{class_name.camelize}Service")
+ rescue LoadError
+ raise "Missing service adapter for #{class_name.inspect}"
end
end
end
diff --git a/activestorage/lib/active_storage/service/disk_service.rb b/activestorage/lib/active_storage/service/disk_service.rb
index 5b652fe74e..52f3a3df16 100644
--- a/activestorage/lib/active_storage/service/disk_service.rb
+++ b/activestorage/lib/active_storage/service/disk_service.rb
@@ -22,27 +22,31 @@ module ActiveStorage
end
end
- def download(key)
+ def download(key, &block)
if block_given?
instrument :streaming_download, key: key do
- File.open(path_for(key), "rb") do |file|
- while data = file.read(64.kilobytes)
- yield data
- end
- end
+ stream key, &block
end
else
instrument :download, key: key do
- File.binread path_for(key)
+ begin
+ File.binread path_for(key)
+ rescue Errno::ENOENT
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- File.open(path_for(key), "rb") do |file|
- file.seek range.begin
- file.read range.size
+ begin
+ File.open(path_for(key), "rb") do |file|
+ file.seek range.begin
+ file.read range.size
+ end
+ rescue Errno::ENOENT
+ raise ActiveStorage::FileNotFoundError
end
end
end
@@ -117,9 +121,19 @@ module ActiveStorage
{ "Content-Type" => content_type }
end
+ def path_for(key) #:nodoc:
+ File.join root, folder_for(key), key
+ end
+
private
- def path_for(key)
- File.join root, folder_for(key), key
+ def stream(key)
+ File.open(path_for(key), "rb") do |file|
+ while data = file.read(5.megabytes)
+ yield data
+ end
+ end
+ rescue Errno::ENOENT
+ raise ActiveStorage::FileNotFoundError
end
def folder_for(key)
diff --git a/activestorage/lib/active_storage/service/gcs_service.rb b/activestorage/lib/active_storage/service/gcs_service.rb
index 369c33cbdb..18c0f14cfc 100644
--- a/activestorage/lib/active_storage/service/gcs_service.rb
+++ b/activestorage/lib/active_storage/service/gcs_service.rb
@@ -1,12 +1,7 @@
# frozen_string_literal: true
-gem "google-cloud-storage", "~> 1.8"
-
+gem "google-cloud-storage", "~> 1.11"
require "google/cloud/storage"
-require "net/http"
-
-require "active_support/core_ext/object/to_query"
-require "active_storage/filename"
module ActiveStorage
# Wraps the Google Cloud Storage as an Active Storage service. See ActiveStorage::Service for the generic API
@@ -32,26 +27,28 @@ module ActiveStorage
end
end
- # FIXME: Download in chunks when given a block.
- def download(key)
- instrument :download, key: key do
- io = file_for(key).download
- io.rewind
-
- if block_given?
- yield io.read
- else
- io.read
+ def download(key, &block)
+ if block_given?
+ instrument :streaming_download, key: key do
+ stream(key, &block)
+ end
+ else
+ instrument :download, key: key do
+ begin
+ file_for(key).download.string
+ rescue Google::Cloud::NotFoundError
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- uri = URI(url(key, expires_in: 30.seconds, filename: ActiveStorage::Filename.new(""), content_type: "application/octet-stream", disposition: "inline"))
-
- Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client|
- client.get(uri, "Range" => "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body
+ begin
+ file_for(key).download(range: range).string
+ rescue Google::Cloud::NotFoundError
+ raise ActiveStorage::FileNotFoundError
end
end
end
@@ -68,7 +65,13 @@ module ActiveStorage
def delete_prefixed(prefix)
instrument :delete_prefixed, prefix: prefix do
- bucket.files(prefix: prefix).all(&:delete)
+ bucket.files(prefix: prefix).all do |file|
+ begin
+ file.delete
+ rescue Google::Cloud::NotFoundError
+ # Ignore concurrently-deleted files
+ end
+ end
end
end
@@ -110,8 +113,23 @@ module ActiveStorage
private
attr_reader :config
- def file_for(key)
- bucket.file(key, skip_lookup: true)
+ def file_for(key, skip_lookup: true)
+ bucket.file(key, skip_lookup: skip_lookup)
+ end
+
+ # Reads the file for the given key in chunks, yielding each to the block.
+ def stream(key)
+ file = file_for(key, skip_lookup: false)
+
+ chunk_size = 5.megabytes
+ offset = 0
+
+ raise ActiveStorage::FileNotFoundError unless file.present?
+
+ while offset < file.size
+ yield file.download(range: offset..(offset + chunk_size - 1)).string
+ offset += chunk_size
+ end
end
def bucket
diff --git a/activestorage/lib/active_storage/service/s3_service.rb b/activestorage/lib/active_storage/service/s3_service.rb
index 5e489f4be1..89a9e54158 100644
--- a/activestorage/lib/active_storage/service/s3_service.rb
+++ b/activestorage/lib/active_storage/service/s3_service.rb
@@ -33,14 +33,22 @@ module ActiveStorage
end
else
instrument :download, key: key do
- object_for(key).get.body.read.force_encoding(Encoding::BINARY)
+ begin
+ object_for(key).get.body.string.force_encoding(Encoding::BINARY)
+ rescue Aws::S3::Errors::NoSuchKey
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
end
def download_chunk(key, range)
instrument :download_chunk, key: key, range: range do
- object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY)
+ begin
+ object_for(key).get(range: "bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}").body.read.force_encoding(Encoding::BINARY)
+ rescue Aws::S3::Errors::NoSuchKey
+ raise ActiveStorage::FileNotFoundError
+ end
end
end
@@ -103,6 +111,8 @@ module ActiveStorage
chunk_size = 5.megabytes
offset = 0
+ raise ActiveStorage::FileNotFoundError unless object.exists?
+
while offset < object.content_length
yield object.get(range: "bytes=#{offset}-#{offset + chunk_size - 1}").body.read.force_encoding(Encoding::BINARY)
offset += chunk_size
diff --git a/activestorage/lib/active_storage/transformers/image_processing_transformer.rb b/activestorage/lib/active_storage/transformers/image_processing_transformer.rb
new file mode 100644
index 0000000000..7f8685b72d
--- /dev/null
+++ b/activestorage/lib/active_storage/transformers/image_processing_transformer.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require "image_processing"
+
+module ActiveStorage
+ module Transformers
+ class ImageProcessingTransformer < Transformer
+ private
+ def process(file, format:)
+ processor.
+ source(file).
+ loader(page: 0).
+ convert(format).
+ apply(operations).
+ call
+ end
+
+ def processor
+ ImageProcessing.const_get(ActiveStorage.variant_processor.to_s.camelize)
+ end
+
+ def operations
+ transformations.each_with_object([]) do |(name, argument), list|
+ if name.to_s == "combine_options"
+ ActiveSupport::Deprecation.warn <<~WARNING
+ Active Storage's ImageProcessing transformer doesn't support :combine_options,
+ as it always generates a single ImageMagick command. Passing :combine_options will
+ not be supported in Rails 6.1.
+ WARNING
+
+ list.concat argument.keep_if { |key, value| value.present? }.to_a
+ elsif argument.present?
+ list << [ name, argument ]
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/transformers/mini_magick_transformer.rb b/activestorage/lib/active_storage/transformers/mini_magick_transformer.rb
new file mode 100644
index 0000000000..e8e99cea9e
--- /dev/null
+++ b/activestorage/lib/active_storage/transformers/mini_magick_transformer.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require "mini_magick"
+
+module ActiveStorage
+ module Transformers
+ class MiniMagickTransformer < Transformer
+ private
+ def process(file, format:)
+ image = MiniMagick::Image.new(file.path, file)
+
+ transformations.each do |name, argument_or_subtransformations|
+ image.mogrify do |command|
+ if name.to_s == "combine_options"
+ argument_or_subtransformations.each do |subtransformation_name, subtransformation_argument|
+ pass_transform_argument(command, subtransformation_name, subtransformation_argument)
+ end
+ else
+ pass_transform_argument(command, name, argument_or_subtransformations)
+ end
+ end
+ end
+
+ image.format(format) if format
+
+ image.tempfile.tap(&:open)
+ end
+
+ def pass_transform_argument(command, method, argument)
+ if argument == true
+ command.public_send(method)
+ elsif argument.present?
+ command.public_send(method, argument)
+ end
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/active_storage/transformers/transformer.rb b/activestorage/lib/active_storage/transformers/transformer.rb
new file mode 100644
index 0000000000..2e21201004
--- /dev/null
+++ b/activestorage/lib/active_storage/transformers/transformer.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module ActiveStorage
+ module Transformers
+ # A Transformer applies a set of transformations to an image.
+ #
+ # The following concrete subclasses are included in Active Storage:
+ #
+ # * ActiveStorage::Transformers::ImageProcessingTransformer:
+ # backed by ImageProcessing, a common interface for MiniMagick and ruby-vips
+ #
+ # * ActiveStorage::Transformers::MiniMagickTransformer:
+ # backed by MiniMagick, a wrapper around the ImageMagick CLI
+ class Transformer
+ attr_reader :transformations
+
+ def initialize(transformations)
+ @transformations = transformations
+ end
+
+ # Applies the transformations to the source image in +file+, producing a target image in the
+ # specified +format+. Yields an open Tempfile containing the target image. Closes and unlinks
+ # the output tempfile after yielding to the given block. Returns the result of the block.
+ def transform(file, format:)
+ output = process(file, format: format)
+
+ begin
+ yield output
+ ensure
+ output.close!
+ end
+ end
+
+ private
+ # Returns an open Tempfile containing a transformed image in the given +format+.
+ # All subclasses implement this method.
+ def process(file, format:) #:doc:
+ raise NotImplementedError
+ end
+ end
+ end
+end
diff --git a/activestorage/lib/tasks/activestorage.rake b/activestorage/lib/tasks/activestorage.rake
index 296e91afa1..ac254d717f 100644
--- a/activestorage/lib/tasks/activestorage.rake
+++ b/activestorage/lib/tasks/activestorage.rake
@@ -1,6 +1,9 @@
# frozen_string_literal: true
namespace :active_storage do
+ # Prevent migration installation task from showing up twice.
+ Rake::Task["install:migrations"].clear_comments
+
desc "Copy over the migration needed to the application"
task install: :environment do
if Rake::Task.task_defined?("active_storage:install:migrations")
diff --git a/activestorage/package.json b/activestorage/package.json
index d23f8b179e..00876985cf 100644
--- a/activestorage/package.json
+++ b/activestorage/package.json
@@ -22,15 +22,19 @@
},
"devDependencies": {
"babel-core": "^6.25.0",
- "babel-loader": "^7.1.1",
+ "babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.0",
"eslint": "^4.3.0",
"eslint-plugin-import": "^2.7.0",
- "webpack": "^3.4.0"
+ "rollup": "^0.58.2",
+ "rollup-plugin-babel": "^3.0.4",
+ "rollup-plugin-commonjs": "^9.1.0",
+ "rollup-plugin-node-resolve": "^3.3.0",
+ "rollup-plugin-uglify": "^3.0.0"
},
"scripts": {
"prebuild": "yarn lint",
- "build": "webpack -p",
+ "build": "rollup --config rollup.config.js",
"lint": "eslint app/javascript",
"prepublishOnly": "rm -rf src && cp -R app/javascript/activestorage src"
}
diff --git a/activestorage/rollup.config.js b/activestorage/rollup.config.js
new file mode 100644
index 0000000000..1b4f9477ab
--- /dev/null
+++ b/activestorage/rollup.config.js
@@ -0,0 +1,28 @@
+import resolve from "rollup-plugin-node-resolve"
+import commonjs from "rollup-plugin-commonjs"
+import babel from "rollup-plugin-babel"
+import uglify from "rollup-plugin-uglify"
+
+const uglifyOptions = {
+ mangle: false,
+ compress: false,
+ output: {
+ beautify: true,
+ indent_level: 2
+ }
+}
+
+export default {
+ input: "app/javascript/activestorage/index.js",
+ output: {
+ file: "app/assets/javascripts/activestorage.js",
+ format: "umd",
+ name: "ActiveStorage"
+ },
+ plugins: [
+ resolve(),
+ commonjs(),
+ babel(),
+ uglify(uglifyOptions)
+ ]
+}
diff --git a/activestorage/test/analyzer/image_analyzer_test.rb b/activestorage/test/analyzer/image_analyzer_test.rb
index f8a38f001a..55bb5e7280 100644
--- a/activestorage/test/analyzer/image_analyzer_test.rb
+++ b/activestorage/test/analyzer/image_analyzer_test.rb
@@ -29,9 +29,4 @@ class ActiveStorage::Analyzer::ImageAnalyzerTest < ActiveSupport::TestCase
assert_equal 792, metadata[:width]
assert_equal 584, metadata[:height]
end
-
- private
- def extract_metadata_from(blob)
- blob.tap(&:analyze).metadata
- end
end
diff --git a/activestorage/test/analyzer/video_analyzer_test.rb b/activestorage/test/analyzer/video_analyzer_test.rb
index 2612006551..d30f49315a 100644
--- a/activestorage/test/analyzer/video_analyzer_test.rb
+++ b/activestorage/test/analyzer/video_analyzer_test.rb
@@ -51,9 +51,4 @@ class ActiveStorage::Analyzer::VideoAnalyzerTest < ActiveSupport::TestCase
metadata = extract_metadata_from(blob)
assert_equal({ "analyzed" => true, "identified" => true }, metadata)
end
-
- private
- def extract_metadata_from(blob)
- blob.tap(&:analyze).metadata
- end
end
diff --git a/activestorage/test/controllers/disk_controller_test.rb b/activestorage/test/controllers/disk_controller_test.rb
index 940dbf5918..4bc61d13f3 100644
--- a/activestorage/test/controllers/disk_controller_test.rb
+++ b/activestorage/test/controllers/disk_controller_test.rb
@@ -6,18 +6,37 @@ require "database/setup"
class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
test "showing blob inline" do
blob = create_blob
-
get blob.service_url
- assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"]
- assert_equal "text/plain", @response.headers["Content-Type"]
+ assert_response :ok
+ assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
+ assert_equal "text/plain", response.headers["Content-Type"]
+ assert_equal "Hello world!", response.body
end
test "showing blob as attachment" do
blob = create_blob
-
get blob.service_url(disposition: :attachment)
- assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", @response.headers["Content-Disposition"]
- assert_equal "text/plain", @response.headers["Content-Type"]
+ assert_response :ok
+ assert_equal "attachment; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
+ assert_equal "text/plain", response.headers["Content-Type"]
+ assert_equal "Hello world!", response.body
+ end
+
+ test "showing blob range inline" do
+ blob = create_blob
+ get blob.service_url, headers: { "Range" => "bytes=5-9" }
+ assert_response :partial_content
+ assert_equal "inline; filename=\"hello.txt\"; filename*=UTF-8''hello.txt", response.headers["Content-Disposition"]
+ assert_equal "text/plain", response.headers["Content-Type"]
+ assert_equal " worl", response.body
+ end
+
+ test "showing blob that does not exist" do
+ blob = create_blob
+ blob.delete
+
+ get blob.service_url
+ assert_response :not_found
end
@@ -56,4 +75,10 @@ class ActiveStorage::DiskControllerTest < ActionDispatch::IntegrationTest
assert_response :unprocessable_entity
assert_not blob.service.exist?(blob.key)
end
+
+ test "directly uploading blob with invalid token" do
+ put update_rails_disk_service_url(encoded_token: "invalid"),
+ params: "Something else entirely!", headers: { "Content-Type" => "text/plain" }
+ assert_response :not_found
+ end
end
diff --git a/activestorage/test/dummy/config/secrets.yml b/activestorage/test/dummy/config/secrets.yml
index 77d1fc383a..18ada4405e 100644
--- a/activestorage/test/dummy/config/secrets.yml
+++ b/activestorage/test/dummy/config/secrets.yml
@@ -25,7 +25,7 @@ test:
# Do not keep production secrets in the unencrypted secrets file.
# Instead, either read values from the environment.
-# Or, use `bin/rails secrets:setup` to configure encrypted secrets
+# Or, use `rails secrets:setup` to configure encrypted secrets
# and move the `production:` environment over there.
production:
diff --git a/activestorage/test/jobs/purge_job_test.rb b/activestorage/test/jobs/purge_job_test.rb
new file mode 100644
index 0000000000..251022a96f
--- /dev/null
+++ b/activestorage/test/jobs/purge_job_test.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::PurgeJobTest < ActiveJob::TestCase
+ setup { @blob = create_blob }
+
+ test "purges" do
+ assert_difference -> { ActiveStorage::Blob.count }, -1 do
+ ActiveStorage::PurgeJob.perform_now @blob
+ end
+
+ assert_not ActiveStorage::Blob.exists?(@blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(@blob.key)
+ end
+
+ test "ignores missing blob" do
+ @blob.purge
+
+ perform_enqueued_jobs do
+ assert_nothing_raised do
+ ActiveStorage::PurgeJob.perform_later @blob
+ end
+ end
+ end
+end
diff --git a/activestorage/test/models/attached/many_test.rb b/activestorage/test/models/attached/many_test.rb
new file mode 100644
index 0000000000..3b563b3fc8
--- /dev/null
+++ b/activestorage/test/models/attached/many_test.rb
@@ -0,0 +1,596 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+
+ setup do
+ @user = User.create!(name: "Josh")
+ end
+
+ teardown { ActiveStorage::Blob.all.each(&:delete) }
+
+ test "attaching existing blobs to an existing record" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs from signed IDs to an existing record" do
+ @user.highlights.attach create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from Hashes to an existing record" do
+ @user.highlights.attach(
+ { io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("THINGS"), filename: "town.jpg", content_type: "image/jpeg" })
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from uploaded files to an existing record" do
+ @user.highlights.attach fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4")
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs from signed IDs to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from Hashes to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach(
+ { io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("THINGS"), filename: "town.jpg", content_type: "image/jpeg" })
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching new blobs from uploaded files to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.highlights.attach fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4")
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not @user.highlights.first.persisted?
+ assert_not @user.highlights.second.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "racecar.jpg", @user.highlights.reload.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ end
+
+ test "attaching existing blobs to an existing record one at a time" do
+ @user.highlights.attach create_blob(filename: "funky.jpg")
+ @user.highlights.attach create_blob(filename: "town.jpg")
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+
+ @user.reload
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "updating an existing record to attach existing blobs" do
+ @user.update! highlights: [ create_file_blob(filename: "racecar.jpg"), create_file_blob(filename: "video.mp4") ]
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ end
+
+ test "updating an existing record to attach existing blobs from signed IDs" do
+ @user.update! highlights: [ create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id ]
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ end
+
+ test "successfully updating an existing record to attach new blobs from uploaded files" do
+ @user.highlights = [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ]
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+
+ @user.save!
+ assert ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+ end
+
+ test "unsuccessfully updating an existing record to attach new blobs from uploaded files" do
+ assert_not @user.update(name: "", highlights: [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ])
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+ end
+
+ test "replacing existing, dependent attachments on an existing record via assign and attach" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |old_blobs|
+ @user.highlights.attach old_blobs
+
+ @user.highlights = []
+ assert_not @user.highlights.attached?
+
+ perform_enqueued_jobs do
+ @user.highlights.attach create_blob(filename: "whenever.jpg"), create_blob(filename: "wherever.jpg")
+ end
+
+ assert_equal "whenever.jpg", @user.highlights.first.filename.to_s
+ assert_equal "wherever.jpg", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(old_blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.second.key)
+ end
+ end
+
+ test "replacing existing, independent attachments on an existing record via assign and attach" do
+ @user.vlogs.attach create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4")
+
+ @user.vlogs = []
+ assert_not @user.vlogs.attached?
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.vlogs.attach create_blob(filename: "whenever.mp4"), create_blob(filename: "wherever.mp4")
+ end
+
+ assert_equal "whenever.mp4", @user.vlogs.first.filename.to_s
+ assert_equal "wherever.mp4", @user.vlogs.second.filename.to_s
+ end
+
+ test "successfully updating an existing record to replace existing, dependent attachments" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |old_blobs|
+ @user.highlights.attach old_blobs
+
+ perform_enqueued_jobs do
+ @user.update! highlights: [ create_blob(filename: "whenever.jpg"), create_blob(filename: "wherever.jpg") ]
+ end
+
+ assert_equal "whenever.jpg", @user.highlights.first.filename.to_s
+ assert_equal "wherever.jpg", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(old_blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(old_blobs.second.key)
+ end
+ end
+
+ test "successfully updating an existing record to replace existing, independent attachments" do
+ @user.vlogs.attach create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4")
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! vlogs: [ create_blob(filename: "whenever.mp4"), create_blob(filename: "wherever.mp4") ]
+ end
+
+ assert_equal "whenever.mp4", @user.vlogs.first.filename.to_s
+ assert_equal "wherever.mp4", @user.vlogs.second.filename.to_s
+ end
+
+ test "unsuccessfully updating an existing record to replace existing attachments" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+
+ assert_no_enqueued_jobs do
+ assert_not @user.update(name: "", highlights: [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ])
+ end
+
+ assert_equal "racecar.jpg", @user.highlights.first.filename.to_s
+ assert_equal "video.mp4", @user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(@user.highlights.second.key)
+ end
+
+ test "updating an existing record to attach one new blob and one previously-attached blob" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs.first
+
+ perform_enqueued_jobs do
+ assert_no_changes -> { @user.highlights_attachments.first.id } do
+ @user.update! highlights: blobs
+ end
+ end
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(@user.highlights.first.key)
+ end
+ end
+
+ test "updating an existing record to remove dependent attachments" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blobs.first ] do
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blobs.second ] do
+ @user.update! highlights: []
+ end
+ end
+
+ assert_not @user.highlights.attached?
+ end
+ end
+
+ test "updating an existing record to remove independent attachments" do
+ [ create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4") ].tap do |blobs|
+ @user.vlogs.attach blobs
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! vlogs: []
+ end
+
+ assert_not @user.vlogs.attached?
+ end
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.highlights.attach fixture_file_upload("racecar.jpg")
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record via update" do
+ perform_enqueued_jobs do
+ @user.update! highlights: [ fixture_file_upload("racecar.jpg") ]
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.highlights.attach directly_upload_file_blob(filename: "racecar.jpg")
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record via update" do
+ perform_enqueued_jobs do
+ @user.update! highlights: [ directly_upload_file_blob(filename: "racecar.jpg") ]
+ end
+
+ assert @user.highlights.reload.first.analyzed?
+ assert_equal 4104, @user.highlights.first.metadata[:width]
+ assert_equal 2736, @user.highlights.first.metadata[:height]
+ end
+
+ test "attaching existing blobs to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+ assert user.new_record?
+ assert_equal "funky.jpg", user.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+ end
+ end
+
+ test "attaching an existing blob from a signed ID to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert user.new_record?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "attaching new blobs from Hashes to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.highlights.attach(
+ { io: StringIO.new("STUFF"), filename: "funky.jpg", content_type: "image/jpg" },
+ { io: StringIO.new("THINGS"), filename: "town.jpg", content_type: "image/jpg" })
+
+ assert user.new_record?
+ assert user.highlights.first.new_record?
+ assert user.highlights.second.new_record?
+ assert user.highlights.first.blob.new_record?
+ assert user.highlights.second.blob.new_record?
+ assert_equal "funky.jpg", user.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+
+ user.save!
+ assert user.highlights.first.persisted?
+ assert user.highlights.second.persisted?
+ assert user.highlights.first.blob.persisted?
+ assert user.highlights.second.blob.persisted?
+ assert_equal "funky.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.highlights.second.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+ end
+ end
+
+ test "attaching new blobs from uploaded files to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.highlights.attach fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4")
+ assert user.new_record?
+ assert user.highlights.first.new_record?
+ assert user.highlights.second.new_record?
+ assert user.highlights.first.blob.new_record?
+ assert user.highlights.second.blob.new_record?
+ assert_equal "racecar.jpg", user.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+
+ user.save!
+ assert user.highlights.first.persisted?
+ assert user.highlights.second.persisted?
+ assert user.highlights.first.blob.persisted?
+ assert user.highlights.second.blob.persisted?
+ assert_equal "racecar.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+ end
+ end
+
+ test "creating a record with existing blobs attached" do
+ user = User.create!(name: "Jason", highlights: [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ])
+ assert_equal "funky.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.reload.highlights.second.filename.to_s
+ end
+
+ test "creating a record with an existing blob from signed IDs attached" do
+ user = User.create!(name: "Jason", highlights: [
+ create_blob(filename: "funky.jpg").signed_id, create_blob(filename: "town.jpg").signed_id ])
+ assert_equal "funky.jpg", user.reload.highlights.first.filename.to_s
+ assert_equal "town.jpg", user.reload.highlights.second.filename.to_s
+ end
+
+ test "creating a record with new blobs from uploaded files attached" do
+ User.new(name: "Jason", highlights: [ fixture_file_upload("racecar.jpg"), fixture_file_upload("video.mp4") ]).tap do |user|
+ assert user.new_record?
+ assert user.highlights.first.new_record?
+ assert user.highlights.second.new_record?
+ assert user.highlights.first.blob.new_record?
+ assert user.highlights.second.blob.new_record?
+ assert_equal "racecar.jpg", user.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(user.highlights.second.key)
+
+ user.save!
+ assert_equal "racecar.jpg", user.highlights.first.filename.to_s
+ assert_equal "video.mp4", user.highlights.second.filename.to_s
+ end
+ end
+
+ test "creating a record with an unexpected object attached" do
+ error = assert_raises(ArgumentError) { User.create!(name: "Jason", highlights: :foo) }
+ assert_equal "Could not find or build blob: expected attachable, got :foo", error.message
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", highlights: [ fixture_file_upload("racecar.jpg") ])
+ assert user.highlights.reload.first.analyzed?
+ assert_equal 4104, user.highlights.first.metadata[:width]
+ assert_equal 2736, user.highlights.first.metadata[:height]
+ end
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", highlights: [ directly_upload_file_blob(filename: "racecar.jpg") ])
+ assert user.highlights.reload.first.analyzed?
+ assert_equal 4104, user.highlights.first.metadata[:width]
+ assert_equal 2736, user.highlights.first.metadata[:height]
+ end
+ end
+
+ test "detaching" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ perform_enqueued_jobs do
+ @user.highlights.detach
+ end
+
+ assert_not @user.highlights.attached?
+ assert ActiveStorage::Blob.exists?(blobs.first.id)
+ assert ActiveStorage::Blob.exists?(blobs.second.id)
+ assert ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "purging" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ @user.highlights.purge
+ assert_not @user.highlights.attached?
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "purging attachment with shared blobs" do
+ [
+ create_blob(filename: "funky.jpg"),
+ create_blob(filename: "town.jpg"),
+ create_blob(filename: "worm.jpg")
+ ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ another_user = User.create!(name: "John")
+ shared_blobs = [blobs.second, blobs.third]
+ another_user.highlights.attach shared_blobs
+ assert another_user.highlights.attached?
+
+ @user.highlights.purge
+ assert_not @user.highlights.attached?
+
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert ActiveStorage::Blob.exists?(blobs.second.id)
+ assert ActiveStorage::Blob.exists?(blobs.third.id)
+
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert ActiveStorage::Blob.service.exist?(blobs.second.key)
+ assert ActiveStorage::Blob.service.exist?(blobs.third.key)
+ end
+ end
+
+ test "purging later" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ perform_enqueued_jobs do
+ @user.highlights.purge_later
+ end
+
+ assert_not @user.highlights.attached?
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "purging attachment later with shared blobs" do
+ [
+ create_blob(filename: "funky.jpg"),
+ create_blob(filename: "town.jpg"),
+ create_blob(filename: "worm.jpg")
+ ].tap do |blobs|
+ @user.highlights.attach blobs
+ assert @user.highlights.attached?
+
+ another_user = User.create!(name: "John")
+ shared_blobs = [blobs.second, blobs.third]
+ another_user.highlights.attach shared_blobs
+ assert another_user.highlights.attached?
+
+ perform_enqueued_jobs do
+ @user.highlights.purge_later
+ end
+
+ assert_not @user.highlights.attached?
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert ActiveStorage::Blob.exists?(blobs.second.id)
+ assert ActiveStorage::Blob.exists?(blobs.third.id)
+
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert ActiveStorage::Blob.service.exist?(blobs.second.key)
+ assert ActiveStorage::Blob.service.exist?(blobs.third.key)
+ end
+ end
+
+ test "purging dependent attachment later on destroy" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ @user.highlights.attach blobs
+
+ perform_enqueued_jobs do
+ @user.destroy!
+ end
+
+ assert_not ActiveStorage::Blob.exists?(blobs.first.id)
+ assert_not ActiveStorage::Blob.exists?(blobs.second.id)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.first.key)
+ assert_not ActiveStorage::Blob.service.exist?(blobs.second.key)
+ end
+ end
+
+ test "not purging independent attachment on destroy" do
+ [ create_blob(filename: "funky.mp4"), create_blob(filename: "town.mp4") ].tap do |blobs|
+ @user.vlogs.attach blobs
+
+ assert_no_enqueued_jobs do
+ @user.destroy!
+ end
+ end
+ end
+
+ test "clearing change on reload" do
+ @user.highlights = [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ]
+ assert @user.highlights.attached?
+
+ @user.reload
+ assert_not @user.highlights.attached?
+ end
+
+ test "overriding attached reader" do
+ @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg")
+
+ assert_equal "funky.jpg", @user.highlights.first.filename.to_s
+ assert_equal "town.jpg", @user.highlights.second.filename.to_s
+
+ begin
+ User.class_eval do
+ def highlights
+ super.reverse
+ end
+ end
+
+ assert_equal "town.jpg", @user.highlights.first.filename.to_s
+ assert_equal "funky.jpg", @user.highlights.second.filename.to_s
+ ensure
+ User.send(:remove_method, :highlights)
+ end
+ end
+end
diff --git a/activestorage/test/models/attached/one_test.rb b/activestorage/test/models/attached/one_test.rb
new file mode 100644
index 0000000000..561c3e9d23
--- /dev/null
+++ b/activestorage/test/models/attached/one_test.rb
@@ -0,0 +1,513 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase
+ include ActiveJob::TestHelper
+
+ setup do
+ @user = User.create!(name: "Josh")
+ end
+
+ teardown { ActiveStorage::Blob.all.each(&:delete) }
+
+ test "attaching an existing blob to an existing record" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching an existing blob from a signed ID to an existing record" do
+ @user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from a Hash to an existing record" do
+ @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from an uploaded file to an existing record" do
+ @user.avatar.attach fixture_file_upload("racecar.jpg")
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ end
+
+ test "attaching an existing blob to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "attaching an existing blob from a signed ID to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from a Hash to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "town.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "attaching a new blob from an uploaded file to an existing, changed record" do
+ @user.name = "Tina"
+ assert @user.changed?
+
+ @user.avatar.attach fixture_file_upload("racecar.jpg")
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not @user.avatar.persisted?
+ assert @user.will_save_change_to_name?
+
+ @user.save!
+ assert_equal "racecar.jpg", @user.reload.avatar.filename.to_s
+ end
+
+ test "updating an existing record to attach an existing blob" do
+ @user.update! avatar: create_blob(filename: "funky.jpg")
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "updating an existing record to attach an existing blob from a signed ID" do
+ @user.update! avatar: create_blob(filename: "funky.jpg").signed_id
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ end
+
+ test "successfully updating an existing record to attach a new blob from an uploaded file" do
+ @user.avatar = fixture_file_upload("racecar.jpg")
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.avatar.key)
+
+ @user.save!
+ assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+
+ test "unsuccessfully updating an existing record to attach a new blob from an uploaded file" do
+ assert_not @user.update(name: "", avatar: fixture_file_upload("racecar.jpg"))
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+
+ test "successfully replacing an existing, dependent attachment on an existing record" do
+ create_blob(filename: "funky.jpg").tap do |old_blob|
+ @user.avatar.attach old_blob
+
+ perform_enqueued_jobs do
+ @user.avatar.attach create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blob.key)
+ end
+ end
+
+ test "replacing an existing, independent attachment on an existing record" do
+ @user.cover_photo.attach create_blob(filename: "funky.jpg")
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.cover_photo.attach create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.cover_photo.filename.to_s
+ end
+
+ test "replacing an attached blob on an existing record with itself" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_no_changes -> { @user.reload.avatar_attachment.id } do
+ assert_no_enqueued_jobs do
+ @user.avatar.attach blob
+ end
+ end
+
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+ end
+
+ test "successfully updating an existing record to replace an existing, dependent attachment" do
+ create_blob(filename: "funky.jpg").tap do |old_blob|
+ @user.avatar.attach old_blob
+
+ perform_enqueued_jobs do
+ @user.update! avatar: create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.exists?(old_blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(old_blob.key)
+ end
+ end
+
+ test "successfully updating an existing record to replace an existing, independent attachment" do
+ @user.cover_photo.attach create_blob(filename: "funky.jpg")
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! cover_photo: create_blob(filename: "town.jpg")
+ end
+
+ assert_equal "town.jpg", @user.cover_photo.filename.to_s
+ end
+
+ test "unsuccessfully updating an existing record to replace an existing attachment" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+
+ assert_no_enqueued_jobs do
+ assert_not @user.update(name: "", avatar: fixture_file_upload("racecar.jpg"))
+ end
+
+ assert_equal "racecar.jpg", @user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(@user.avatar.key)
+ end
+
+ test "updating an existing record to replace an attached blob with itself" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_no_enqueued_jobs do
+ assert_no_changes -> { @user.reload.avatar_attachment.id } do
+ @user.update! avatar: blob
+ end
+ end
+ end
+ end
+
+ test "removing a dependent attachment from an existing record" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blob ] do
+ @user.avatar.attach nil
+ end
+
+ assert_not @user.avatar.attached?
+ end
+ end
+
+ test "removing an independent attachment from an existing record" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.cover_photo.attach blob
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.cover_photo.attach nil
+ end
+
+ assert_not @user.cover_photo.attached?
+ end
+ end
+
+ test "updating an existing record to remove a dependent attachment" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ assert_enqueued_with job: ActiveStorage::PurgeJob, args: [ blob ] do
+ @user.update! avatar: nil
+ end
+
+ assert_not @user.avatar.attached?
+ end
+ end
+
+ test "updating an existing record to remove an independent attachment" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.cover_photo.attach blob
+
+ assert_no_enqueued_jobs only: ActiveStorage::PurgeJob do
+ @user.update! cover_photo: nil
+ end
+
+ assert_not @user.cover_photo.attached?
+ end
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.avatar.attach fixture_file_upload("racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to an existing record via update" do
+ perform_enqueued_jobs do
+ @user.update! avatar: fixture_file_upload("racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record" do
+ perform_enqueued_jobs do
+ @user.avatar.attach directly_upload_file_blob(filename: "racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to an existing record via updates" do
+ perform_enqueued_jobs do
+ @user.update! avatar: directly_upload_file_blob(filename: "racecar.jpg")
+ end
+
+ assert @user.avatar.reload.analyzed?
+ assert_equal 4104, @user.avatar.metadata[:width]
+ assert_equal 2736, @user.avatar.metadata[:height]
+ end
+
+ test "attaching an existing blob to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach create_blob(filename: "funky.jpg")
+ assert user.new_record?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "attaching an existing blob from a signed ID to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach create_blob(filename: "funky.jpg").signed_id
+ assert user.new_record?
+ assert_equal "funky.jpg", user.avatar.filename.to_s
+
+ user.save!
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "attaching a new blob from a Hash to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
+ assert user.new_record?
+ assert user.avatar.attachment.new_record?
+ assert user.avatar.blob.new_record?
+ assert_equal "town.jpg", user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
+
+ user.save!
+ assert user.avatar.attachment.persisted?
+ assert user.avatar.blob.persisted?
+ assert_equal "town.jpg", user.reload.avatar.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.avatar.key)
+ end
+ end
+
+ test "attaching a new blob from an uploaded file to a new record" do
+ User.new(name: "Jason").tap do |user|
+ user.avatar.attach fixture_file_upload("racecar.jpg")
+ assert user.new_record?
+ assert user.avatar.attachment.new_record?
+ assert user.avatar.blob.new_record?
+ assert_equal "racecar.jpg", user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
+
+ user.save!
+ assert user.avatar.attachment.persisted?
+ assert user.avatar.blob.persisted?
+ assert_equal "racecar.jpg", user.reload.avatar.filename.to_s
+ assert ActiveStorage::Blob.service.exist?(user.avatar.key)
+ end
+ end
+
+ test "creating a record with an existing blob attached" do
+ user = User.create!(name: "Jason", avatar: create_blob(filename: "funky.jpg"))
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+
+ test "creating a record with an existing blob from a signed ID attached" do
+ user = User.create!(name: "Jason", avatar: create_blob(filename: "funky.jpg").signed_id)
+ assert_equal "funky.jpg", user.reload.avatar.filename.to_s
+ end
+
+ test "creating a record with a new blob from an uploaded file attached" do
+ User.new(name: "Jason", avatar: fixture_file_upload("racecar.jpg")).tap do |user|
+ assert user.new_record?
+ assert user.avatar.attachment.new_record?
+ assert user.avatar.blob.new_record?
+ assert_equal "racecar.jpg", user.avatar.filename.to_s
+ assert_not ActiveStorage::Blob.service.exist?(user.avatar.key)
+
+ user.save!
+ assert_equal "racecar.jpg", user.reload.avatar.filename.to_s
+ end
+ end
+
+ test "creating a record with an unexpected object attached" do
+ error = assert_raises(ArgumentError) { User.create!(name: "Jason", avatar: :foo) }
+ assert_equal "Could not find or build blob: expected attachable, got :foo", error.message
+ end
+
+ test "analyzing a new blob from an uploaded file after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", avatar: fixture_file_upload("racecar.jpg"))
+ assert user.avatar.reload.analyzed?
+ assert_equal 4104, user.avatar.metadata[:width]
+ assert_equal 2736, user.avatar.metadata[:height]
+ end
+ end
+
+ test "analyzing a directly-uploaded blob after attaching it to a new record" do
+ perform_enqueued_jobs do
+ user = User.create!(name: "Jason", avatar: directly_upload_file_blob(filename: "racecar.jpg"))
+ assert user.avatar.reload.analyzed?
+ assert_equal 4104, user.avatar.metadata[:width]
+ assert_equal 2736, user.avatar.metadata[:height]
+ end
+ end
+
+ test "detaching" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ perform_enqueued_jobs do
+ @user.avatar.detach
+ end
+
+ assert_not @user.avatar.attached?
+ assert ActiveStorage::Blob.exists?(blob.id)
+ assert ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ @user.avatar.purge
+ assert_not @user.avatar.attached?
+ assert_not ActiveStorage::Blob.exists?(blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging an attachment with a shared blob" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ another_user = User.create!(name: "John")
+ another_user.avatar.attach blob
+ assert another_user.avatar.attached?
+
+ @user.avatar.purge
+ assert_not @user.avatar.attached?
+ assert ActiveStorage::Blob.exists?(blob.id)
+ assert ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging later" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ perform_enqueued_jobs do
+ @user.avatar.purge_later
+ end
+
+ assert_not @user.avatar.attached?
+ assert_not ActiveStorage::Blob.exists?(blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging an attachment later with shared blob" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+ assert @user.avatar.attached?
+
+ another_user = User.create!(name: "John")
+ another_user.avatar.attach blob
+ assert another_user.avatar.attached?
+
+ perform_enqueued_jobs do
+ @user.avatar.purge_later
+ end
+
+ assert_not @user.avatar.attached?
+ assert ActiveStorage::Blob.exists?(blob.id)
+ assert ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "purging dependent attachment later on destroy" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.avatar.attach blob
+
+ perform_enqueued_jobs do
+ @user.destroy!
+ end
+
+ assert_not ActiveStorage::Blob.exists?(blob.id)
+ assert_not ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
+ test "not purging independent attachment on destroy" do
+ create_blob(filename: "funky.jpg").tap do |blob|
+ @user.cover_photo.attach blob
+
+ assert_no_enqueued_jobs do
+ @user.destroy!
+ end
+ end
+ end
+
+ test "clearing change on reload" do
+ @user.avatar = create_blob(filename: "funky.jpg")
+ assert @user.avatar.attached?
+
+ @user.reload
+ assert_not @user.avatar.attached?
+ end
+
+ test "overriding attached reader" do
+ @user.avatar.attach create_blob(filename: "funky.jpg")
+
+ assert_equal "funky.jpg", @user.avatar.filename.to_s
+
+ begin
+ User.class_eval do
+ def avatar
+ super.filename.to_s.reverse
+ end
+ end
+
+ assert_equal "gpj.yknuf", @user.avatar
+ ensure
+ User.send(:remove_method, :avatar)
+ end
+ end
+end
diff --git a/activestorage/test/models/attachments_test.rb b/activestorage/test/models/attachments_test.rb
deleted file mode 100644
index ce83ec27d2..0000000000
--- a/activestorage/test/models/attachments_test.rb
+++ /dev/null
@@ -1,459 +0,0 @@
-# frozen_string_literal: true
-
-require "test_helper"
-require "database/setup"
-
-class ActiveStorage::AttachmentsTest < ActiveSupport::TestCase
- include ActiveJob::TestHelper
-
- setup { @user = User.create!(name: "DHH") }
-
- teardown { ActiveStorage::Blob.all.each(&:purge) }
-
- test "attach existing blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- assert_equal "funky.jpg", @user.avatar.filename.to_s
- end
-
- test "attach existing blob from a signed ID" do
- @user.avatar.attach create_blob(filename: "funky.jpg").signed_id
- assert_equal "funky.jpg", @user.avatar.filename.to_s
- end
-
- test "attach new blob from a Hash" do
- @user.avatar.attach io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg"
- assert_equal "town.jpg", @user.avatar.filename.to_s
- end
-
- test "attach new blob from an UploadedFile" do
- file = file_fixture "racecar.jpg"
- @user.avatar.attach Rack::Test::UploadedFile.new file.to_s
- assert_equal "racecar.jpg", @user.avatar.filename.to_s
- end
-
- test "replace attached blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- perform_enqueued_jobs do
- assert_no_difference -> { ActiveStorage::Blob.count } do
- @user.avatar.attach create_blob(filename: "town.jpg")
- end
- end
-
- assert_equal "town.jpg", @user.avatar.filename.to_s
- end
-
- test "replace attached blob unsuccessfully" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- perform_enqueued_jobs do
- assert_raises do
- @user.avatar.attach nil
- end
- end
-
- assert_equal "funky.jpg", @user.reload.avatar.filename.to_s
- assert ActiveStorage::Blob.service.exist?(@user.avatar.key)
- end
-
- test "replace attached blob with itself" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- assert_no_changes -> { @user.reload.avatar.blob } do
- assert_no_changes -> { @user.reload.avatar.attachment } do
- assert_no_enqueued_jobs do
- @user.avatar.attach @user.avatar.blob
- end
- end
- end
- end
-
- test "replaced attached blob with itself by signed ID" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
-
- assert_no_changes -> { @user.reload.avatar.blob } do
- assert_no_changes -> { @user.reload.avatar.attachment } do
- assert_no_enqueued_jobs do
- @user.avatar.attach @user.avatar.blob.signed_id
- end
- end
- end
- end
-
- test "replace independent attached blob" do
- @user.cover_photo.attach create_blob(filename: "funky.jpg")
-
- perform_enqueued_jobs do
- assert_difference -> { ActiveStorage::Blob.count }, +1 do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- @user.cover_photo.attach create_blob(filename: "town.jpg")
- end
- end
- end
-
- assert_equal "town.jpg", @user.cover_photo.filename.to_s
- end
-
- test "attach blob to new record" do
- user = User.new(name: "Jason")
-
- assert_no_changes -> { user.new_record? } do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- user.avatar.attach create_blob(filename: "funky.jpg")
- end
- end
-
- assert_predicate user.avatar, :attached?
- assert_equal "funky.jpg", user.avatar.filename.to_s
-
- assert_difference -> { ActiveStorage::Attachment.count }, +1 do
- user.save!
- end
-
- assert_predicate user.reload.avatar, :attached?
- assert_equal "funky.jpg", user.avatar.filename.to_s
- end
-
- test "build new record with attached blob" do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- @user = User.new(name: "Jason", avatar: { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" })
- end
-
- assert_predicate @user, :new_record?
- assert_predicate @user.avatar, :attached?
- assert_equal "town.jpg", @user.avatar.filename.to_s
-
- @user.save!
- assert_predicate @user.reload.avatar, :attached?
- assert_equal "town.jpg", @user.avatar.filename.to_s
- end
-
- test "access underlying associations of new blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- assert_equal @user, @user.avatar_attachment.record
- assert_equal @user.avatar_attachment.blob, @user.avatar_blob
- assert_equal "funky.jpg", @user.avatar_attachment.blob.filename.to_s
- end
-
- test "identify newly-attached, directly-uploaded blob" do
- blob = directly_upload_file_blob(content_type: "application/octet-stream")
-
- @user.avatar.attach(blob)
-
- assert_equal "image/jpeg", @user.avatar.reload.content_type
- assert_predicate @user.avatar, :identified?
- end
-
- test "identify and analyze newly-attached, directly-uploaded blob" do
- blob = directly_upload_file_blob(content_type: "application/octet-stream")
-
- perform_enqueued_jobs do
- @user.avatar.attach blob
- end
-
- assert_equal true, @user.avatar.reload.metadata[:identified]
- assert_equal 4104, @user.avatar.metadata[:width]
- assert_equal 2736, @user.avatar.metadata[:height]
- end
-
- test "identify newly-attached blob only once" do
- blob = create_file_blob
- assert_predicate blob, :identified?
-
- # The blob's backing file is a PNG image. Fudge its content type so we can tell if it's identified when we attach it.
- blob.update! content_type: "application/octet-stream"
-
- @user.avatar.attach blob
- assert_equal "application/octet-stream", blob.content_type
- end
-
- test "analyze newly-attached blob" do
- perform_enqueued_jobs do
- @user.avatar.attach create_file_blob
- end
-
- assert_equal 4104, @user.avatar.reload.metadata[:width]
- assert_equal 2736, @user.avatar.metadata[:height]
- end
-
- test "analyze attached blob only once" do
- blob = create_file_blob
-
- perform_enqueued_jobs do
- @user.avatar.attach blob
- end
-
- assert_predicate blob.reload, :analyzed?
-
- @user.avatar.detach
-
- assert_no_enqueued_jobs do
- @user.reload.avatar.attach blob
- end
- end
-
- test "preserve existing metadata when analyzing a newly-attached blob" do
- blob = create_file_blob(metadata: { foo: "bar" })
-
- perform_enqueued_jobs do
- @user.avatar.attach blob
- end
-
- assert_equal "bar", blob.reload.metadata[:foo]
- end
-
- test "detach blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- avatar_blob_id = @user.avatar.blob.id
- avatar_key = @user.avatar.key
-
- @user.avatar.detach
- assert_not_predicate @user.avatar, :attached?
- assert ActiveStorage::Blob.exists?(avatar_blob_id)
- assert ActiveStorage::Blob.service.exist?(avatar_key)
- end
-
- test "purge attached blob" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- avatar_key = @user.avatar.key
-
- @user.avatar.purge
- assert_not_predicate @user.avatar, :attached?
- assert_not ActiveStorage::Blob.service.exist?(avatar_key)
- end
-
- test "purge attached blob later when the record is destroyed" do
- @user.avatar.attach create_blob(filename: "funky.jpg")
- avatar_key = @user.avatar.key
-
- perform_enqueued_jobs do
- @user.reload.destroy
-
- assert_nil ActiveStorage::Blob.find_by(key: avatar_key)
- assert_not ActiveStorage::Blob.service.exist?(avatar_key)
- end
- end
-
- test "delete attachment for independent blob when record is destroyed" do
- @user.cover_photo.attach create_blob(filename: "funky.jpg")
-
- @user.destroy
- assert_not ActiveStorage::Attachment.exists?(record: @user, name: "cover_photo")
- end
-
- test "find with attached blob" do
- records = %w[alice bob].map do |name|
- User.create!(name: name).tap do |user|
- user.avatar.attach create_blob(filename: "#{name}.jpg")
- end
- end
-
- users = User.where(id: records.map(&:id)).with_attached_avatar.all
-
- assert_equal "alice.jpg", users.first.avatar.filename.to_s
- assert_equal "bob.jpg", users.second.avatar.filename.to_s
- end
-
-
- test "attach existing blobs" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
-
- assert_equal "funky.jpg", @user.highlights.first.filename.to_s
- assert_equal "wonky.jpg", @user.highlights.second.filename.to_s
- end
-
- test "attach new blobs" do
- @user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
-
- assert_equal "town.jpg", @user.highlights.first.filename.to_s
- assert_equal "country.jpg", @user.highlights.second.filename.to_s
- end
-
- test "attach blobs to new record" do
- user = User.new(name: "Jason")
-
- assert_no_changes -> { user.new_record? } do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
- end
- end
-
- assert_predicate user.highlights, :attached?
- assert_equal "town.jpg", user.highlights.first.filename.to_s
- assert_equal "country.jpg", user.highlights.second.filename.to_s
-
- assert_difference -> { ActiveStorage::Attachment.count }, +2 do
- user.save!
- end
-
- assert_predicate user.reload.highlights, :attached?
- assert_equal "town.jpg", user.highlights.first.filename.to_s
- assert_equal "country.jpg", user.highlights.second.filename.to_s
- end
-
- test "build new record with attached blobs" do
- assert_no_difference -> { ActiveStorage::Attachment.count } do
- @user = User.new(name: "Jason", highlights: [
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" }])
- end
-
- assert_predicate @user, :new_record?
- assert_predicate @user.highlights, :attached?
- assert_equal "town.jpg", @user.highlights.first.filename.to_s
- assert_equal "country.jpg", @user.highlights.second.filename.to_s
-
- @user.save!
- assert_predicate @user.reload.highlights, :attached?
- assert_equal "town.jpg", @user.highlights.first.filename.to_s
- assert_equal "country.jpg", @user.highlights.second.filename.to_s
- end
-
- test "find attached blobs" do
- @user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
-
- highlights = User.where(id: @user.id).with_attached_highlights.first.highlights
-
- assert_equal "town.jpg", highlights.first.filename.to_s
- assert_equal "country.jpg", highlights.second.filename.to_s
- end
-
- test "access underlying associations of new blobs" do
- @user.highlights.attach(
- { io: StringIO.new("STUFF"), filename: "town.jpg", content_type: "image/jpg" },
- { io: StringIO.new("IT"), filename: "country.jpg", content_type: "image/jpg" })
-
- assert_equal @user, @user.highlights_attachments.first.record
- assert_equal @user.highlights_attachments.collect(&:blob).sort, @user.highlights_blobs.sort
- assert_equal "town.jpg", @user.highlights_attachments.first.blob.filename.to_s
- end
-
- test "analyze newly-attached blobs" do
- perform_enqueued_jobs do
- @user.highlights.attach(
- create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"),
- create_file_blob(filename: "video.mp4", content_type: "video/mp4"))
- end
-
- assert_equal 4104, @user.highlights.first.metadata[:width]
- assert_equal 2736, @user.highlights.first.metadata[:height]
-
- assert_equal 640, @user.highlights.second.metadata[:width]
- assert_equal 480, @user.highlights.second.metadata[:height]
- end
-
- test "analyze attached blobs only once" do
- blobs = [
- create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg"),
- create_file_blob(filename: "video.mp4", content_type: "video/mp4")
- ]
-
- perform_enqueued_jobs do
- @user.highlights.attach(blobs)
- end
-
- assert blobs.each(&:reload).all?(&:analyzed?)
-
- @user.highlights.attachments.destroy_all
-
- assert_no_enqueued_jobs do
- @user.highlights.attach(blobs)
- end
- end
-
- test "preserve existing metadata when analyzing newly-attached blobs" do
- blobs = [
- create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: { foo: "bar" }),
- create_file_blob(filename: "video.mp4", content_type: "video/mp4", metadata: { foo: "bar" })
- ]
-
- perform_enqueued_jobs do
- @user.highlights.attach(blobs)
- end
-
- blobs.each do |blob|
- assert_equal "bar", blob.reload.metadata[:foo]
- end
- end
-
- test "detach blobs" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
- highlight_blob_ids = @user.highlights.collect { |highlight| highlight.blob.id }
- highlight_keys = @user.highlights.collect(&:key)
-
- @user.highlights.detach
- assert_not_predicate @user.highlights, :attached?
-
- assert ActiveStorage::Blob.exists?(highlight_blob_ids.first)
- assert ActiveStorage::Blob.exists?(highlight_blob_ids.second)
-
- assert ActiveStorage::Blob.service.exist?(highlight_keys.first)
- assert ActiveStorage::Blob.service.exist?(highlight_keys.second)
- end
-
- test "purge attached blobs" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
- highlight_keys = @user.highlights.collect(&:key)
-
- @user.highlights.purge
- assert_not_predicate @user.highlights, :attached?
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first)
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second)
- end
-
- test "purge attached blobs later when the record is destroyed" do
- @user.highlights.attach create_blob(filename: "funky.jpg"), create_blob(filename: "wonky.jpg")
- highlight_keys = @user.highlights.collect(&:key)
-
- perform_enqueued_jobs do
- @user.reload.destroy
-
- assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.first)
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.first)
-
- assert_nil ActiveStorage::Blob.find_by(key: highlight_keys.second)
- assert_not ActiveStorage::Blob.service.exist?(highlight_keys.second)
- end
- end
-
- test "delete attachments for independent blobs when the record is destroyed" do
- @user.vlogs.attach create_blob(filename: "funky.mp4"), create_blob(filename: "wonky.mp4")
-
- @user.destroy
- assert_not ActiveStorage::Attachment.exists?(record: @user, name: "vlogs")
- end
-
- test "selectively purge one attached blob of many" do
- first_blob = create_blob(filename: "funky.jpg")
- second_blob = create_blob(filename: "wonky.jpg")
- attachments = @user.highlights.attach(first_blob, second_blob)
-
- assert_difference -> { ActiveStorage::Blob.count }, -1 do
- @user.highlights.where(id: attachments.first.id).purge
- end
-
- assert_not ActiveStorage::Blob.exists?(key: first_blob.key)
- assert ActiveStorage::Blob.exists?(key: second_blob.key)
- end
-
- test "selectively purge one attached blob of many later" do
- first_blob = create_blob(filename: "funky.jpg")
- second_blob = create_blob(filename: "wonky.jpg")
- attachments = @user.highlights.attach(first_blob, second_blob)
-
- perform_enqueued_jobs do
- assert_difference -> { ActiveStorage::Blob.count }, -1 do
- @user.highlights.where(id: attachments.first.id).purge_later
- end
- end
-
- assert_not ActiveStorage::Blob.exists?(key: first_blob.key)
- assert ActiveStorage::Blob.exists?(key: second_blob.key)
- end
-end
diff --git a/activestorage/test/models/blob_test.rb b/activestorage/test/models/blob_test.rb
index fead17d33a..88c106a08b 100644
--- a/activestorage/test/models/blob_test.rb
+++ b/activestorage/test/models/blob_test.rb
@@ -7,21 +7,15 @@ require "active_support/testing/method_call_assertions"
class ActiveStorage::BlobTest < ActiveSupport::TestCase
include ActiveSupport::Testing::MethodCallAssertions
- test ".unattached scope returns not attached blobs" do
- class UserWithHasOneAttachedDependentFalse < User
- has_one_attached :avatar, dependent: false
+ test "unattached scope" do
+ [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ].tap do |blobs|
+ User.create! name: "DHH", avatar: blobs.first
+ assert_includes ActiveStorage::Blob.unattached, blobs.second
+ assert_not_includes ActiveStorage::Blob.unattached, blobs.first
+
+ User.create! name: "Jason", avatar: blobs.second
+ assert_not_includes ActiveStorage::Blob.unattached, blobs.second
end
-
- ActiveStorage::Blob.delete_all
- blob_1 = create_blob filename: "funky.jpg"
- blob_2 = create_blob filename: "town.jpg"
-
- user = UserWithHasOneAttachedDependentFalse.create!
- user.avatar.attach blob_1
-
- assert_equal [blob_2], ActiveStorage::Blob.unattached
- user.destroy
- assert_equal [blob_1, blob_2].map(&:id).sort, ActiveStorage::Blob.unattached.pluck(:id).sort
end
test "create after upload sets byte size and checksum" do
@@ -43,6 +37,16 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
assert_equal "text/plain", blob.content_type
end
+ test "create after upload extracts content_type from io when no content_type given and identify: false" do
+ blob = create_blob content_type: nil, identify: false
+ assert_equal "text/plain", blob.content_type
+ end
+
+ test "create after upload uses content_type when identify: false" do
+ blob = create_blob data: "Article,dates,analysis\n1, 2, 3", filename: "table.csv", content_type: "text/csv", identify: false
+ assert_equal "text/csv", blob.content_type
+ end
+
test "image?" do
blob = create_file_blob filename: "racecar.jpg"
assert_predicate blob, :image?
@@ -62,7 +66,7 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
end
test "download yields chunks" do
- blob = create_blob data: "a" * 75.kilobytes
+ blob = create_blob data: "a" * 5.0625.megabytes
chunks = []
blob.download do |chunk|
@@ -70,8 +74,42 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
end
assert_equal 2, chunks.size
- assert_equal "a" * 64.kilobytes, chunks.first
- assert_equal "a" * 11.kilobytes, chunks.second
+ assert_equal "a" * 5.megabytes, chunks.first
+ assert_equal "a" * 64.kilobytes, chunks.second
+ end
+
+ test "open with integrity" do
+ create_file_blob(filename: "racecar.jpg").tap do |blob|
+ blob.open do |file|
+ assert file.binmode?
+ assert_equal 0, file.pos
+ assert File.basename(file.path).starts_with?("ActiveStorage-#{blob.id}-")
+ assert file.path.ends_with?(".jpg")
+ assert_equal file_fixture("racecar.jpg").binread, file.read, "Expected downloaded file to match fixture file"
+ end
+ end
+ end
+
+ test "open without integrity" do
+ create_blob(data: "Hello, world!").tap do |blob|
+ blob.update! checksum: Digest::MD5.base64digest("Goodbye, world!")
+
+ assert_raises ActiveStorage::IntegrityError do
+ blob.open { |file| flunk "Expected integrity check to fail" }
+ end
+ end
+ end
+
+ test "open in a custom tempdir" do
+ tempdir = Dir.mktmpdir
+
+ create_file_blob(filename: "racecar.jpg").open(tempdir: tempdir) do |file|
+ assert file.binmode?
+ assert_equal 0, file.pos
+ assert_match(/\.jpg\z/, file.path)
+ assert file.path.starts_with?(tempdir)
+ assert_equal file_fixture("racecar.jpg").binread, file.read, "Expected downloaded file to match fixture file"
+ end
end
test "urls expiring in 5 minutes" do
@@ -107,16 +145,16 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
test "urls allow for custom options" do
blob = create_blob(filename: "original.txt")
- options = [
+ arguments = [
blob.key,
- expires_in: blob.service.url_expires_in,
+ expires_in: ActiveStorage.service_urls_expire_in,
disposition: :inline,
content_type: blob.content_type,
filename: blob.filename,
thumb_size: "300x300",
thumb_mode: "crop"
]
- assert_called_with(blob.service, :url, options) do
+ assert_called_with(blob.service, :url, arguments) do
blob.service_url(thumb_size: "300x300", thumb_mode: "crop")
end
end
@@ -136,6 +174,14 @@ class ActiveStorage::BlobTest < ActiveSupport::TestCase
assert_not ActiveStorage::Blob.service.exist?(variant.key)
end
+ test "purge does nothing when attachments exist" do
+ create_blob.tap do |blob|
+ User.create! name: "DHH", avatar: blob
+ assert_no_difference(-> { ActiveStorage::Blob.count }) { blob.purge }
+ assert ActiveStorage::Blob.service.exist?(blob.key)
+ end
+ end
+
private
def expected_url_for(blob, disposition: :inline, filename: nil)
filename ||= blob.filename
diff --git a/activestorage/test/models/presence_validation_test.rb b/activestorage/test/models/presence_validation_test.rb
new file mode 100644
index 0000000000..13ba3c900d
--- /dev/null
+++ b/activestorage/test/models/presence_validation_test.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require "test_helper"
+require "database/setup"
+
+class ActiveStorage::PresenceValidationTest < ActiveSupport::TestCase
+ class Admin < User; end
+
+ teardown do
+ Admin.clear_validators!
+ end
+
+ test "validates_presence_of has_one_attached" do
+ Admin.validates_presence_of :avatar
+ a = Admin.new(name: "DHH")
+ assert_predicate a, :invalid?
+
+ a.avatar.attach create_blob(filename: "funky.jpg")
+ assert_predicate a, :valid?
+ end
+
+ test "validates_presence_of has_many_attached" do
+ Admin.validates_presence_of :highlights
+ a = Admin.new(name: "DHH")
+ assert_predicate a, :invalid?
+
+ a.highlights.attach create_blob(filename: "funky.jpg")
+ assert_predicate a, :valid?
+ end
+end
diff --git a/activestorage/test/models/preview_test.rb b/activestorage/test/models/preview_test.rb
index 55fdc228c8..e7ae399fb7 100644
--- a/activestorage/test/models/preview_test.rb
+++ b/activestorage/test/models/preview_test.rb
@@ -22,8 +22,8 @@ class ActiveStorage::PreviewTest < ActiveSupport::TestCase
preview = blob.preview(resize: "640x280").processed
assert_predicate preview.image, :attached?
- assert_equal "video.png", preview.image.filename.to_s
- assert_equal "image/png", preview.image.content_type
+ assert_equal "video.jpg", preview.image.filename.to_s
+ assert_equal "image/jpeg", preview.image.content_type
image = read_image(preview.image)
assert_equal 640, image.width
diff --git a/activestorage/test/models/reflection_test.rb b/activestorage/test/models/reflection_test.rb
new file mode 100644
index 0000000000..98606b0617
--- /dev/null
+++ b/activestorage/test/models/reflection_test.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require "test_helper"
+
+class ActiveStorage::ReflectionTest < ActiveSupport::TestCase
+ test "reflecting on a singular attachment" do
+ reflection = User.reflect_on_attachment(:avatar)
+ assert_equal User, reflection.active_record
+ assert_equal :avatar, reflection.name
+ assert_equal :has_one_attached, reflection.macro
+ assert_equal :purge_later, reflection.options[:dependent]
+ end
+
+ test "reflection on a singular attachment with the same name as an attachment on another model" do
+ reflection = Group.reflect_on_attachment(:avatar)
+ assert_equal Group, reflection.active_record
+ end
+
+ test "reflecting on a collection attachment" do
+ reflection = User.reflect_on_attachment(:highlights)
+ assert_equal User, reflection.active_record
+ assert_equal :highlights, reflection.name
+ assert_equal :has_many_attached, reflection.macro
+ assert_equal :purge_later, reflection.options[:dependent]
+ end
+
+ test "reflecting on all attachments" do
+ reflections = User.reflect_on_all_attachments.sort_by(&:name)
+ assert_equal [ User ], reflections.collect(&:active_record).uniq
+ assert_equal %i[ avatar cover_photo highlights vlogs ], reflections.collect(&:name)
+ assert_equal %i[ has_one_attached has_one_attached has_many_attached has_many_attached ], reflections.collect(&:macro)
+ assert_equal [ :purge_later, false, :purge_later, false ], reflections.collect { |reflection| reflection.options[:dependent] }
+ end
+end
diff --git a/activestorage/test/models/variant_test.rb b/activestorage/test/models/variant_test.rb
index 0f3ada25c0..6577f1cd9f 100644
--- a/activestorage/test/models/variant_test.rb
+++ b/activestorage/test/models/variant_test.rb
@@ -25,13 +25,91 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
assert_match(/Gray/, image.colorspace)
end
- test "center-weighted crop of JPEG blob" do
+ test "monochrome with default variant_processor" do
+ begin
+ ActiveStorage.variant_processor = nil
+
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(monochrome: true).processed
+ image = read_image(variant)
+ assert_match(/Gray/, image.colorspace)
+ ensure
+ ActiveStorage.variant_processor = :mini_magick
+ end
+ end
+
+ test "disabled variation of JPEG blob" do
blob = create_file_blob(filename: "racecar.jpg")
- variant = blob.variant(combine_options: {
- gravity: "center",
- resize: "100x100^",
- crop: "100x100+0+0",
- }).processed
+ variant = blob.variant(resize: "100x100", monochrome: false).processed
+ assert_match(/racecar\.jpg/, variant.service_url)
+
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 67, image.height
+ assert_match(/RGB/, image.colorspace)
+ end
+
+ test "disabled variation of JPEG blob with :combine_options" do
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = ActiveSupport::Deprecation.silence do
+ blob.variant(combine_options: {
+ resize: "100x100",
+ monochrome: false
+ }).processed
+ end
+ assert_match(/racecar\.jpg/, variant.service_url)
+
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 67, image.height
+ assert_match(/RGB/, image.colorspace)
+ end
+
+ test "disabled variation using :combine_options" do
+ begin
+ ActiveStorage.variant_processor = nil
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = ActiveSupport::Deprecation.silence do
+ blob.variant(combine_options: {
+ crop: "100x100+0+0",
+ monochrome: false
+ }).processed
+ end
+ assert_match(/racecar\.jpg/, variant.service_url)
+
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 100, image.height
+ assert_match(/RGB/, image.colorspace)
+ ensure
+ ActiveStorage.variant_processor = :mini_magick
+ end
+ end
+
+ test "center-weighted crop of JPEG blob using :combine_options" do
+ begin
+ ActiveStorage.variant_processor = nil
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = ActiveSupport::Deprecation.silence do
+ blob.variant(combine_options: {
+ gravity: "center",
+ resize: "100x100^",
+ crop: "100x100+0+0",
+ }).processed
+ end
+ assert_match(/racecar\.jpg/, variant.service_url)
+
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 100, image.height
+ ensure
+ ActiveStorage.variant_processor = :mini_magick
+ end
+ end
+
+ test "center-weighted crop of JPEG blob using :resize_to_fill" do
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(resize_to_fill: [100, 100]).processed
assert_match(/racecar\.jpg/, variant.service_url)
image = read_image(variant)
@@ -80,4 +158,20 @@ class ActiveStorage::VariantTest < ActiveSupport::TestCase
variant = blob.variant(font: "a" * 10_000).processed
assert_operator variant.service_url.length, :<, 525
end
+
+ test "works for vips processor" do
+ begin
+ ActiveStorage.variant_processor = :vips
+ blob = create_file_blob(filename: "racecar.jpg")
+ variant = blob.variant(thumbnail_image: 100).processed
+
+ image = read_image(variant)
+ assert_equal 100, image.width
+ assert_equal 67, image.height
+ rescue LoadError
+ # libvips not installed
+ ensure
+ ActiveStorage.variant_processor = :mini_magick
+ end
+ end
end
diff --git a/activestorage/test/previewer/video_previewer_test.rb b/activestorage/test/previewer/video_previewer_test.rb
index dba9b0d7e2..9dc350205b 100644
--- a/activestorage/test/previewer/video_previewer_test.rb
+++ b/activestorage/test/previewer/video_previewer_test.rb
@@ -12,12 +12,13 @@ class ActiveStorage::Previewer::VideoPreviewerTest < ActiveSupport::TestCase
test "previewing an MP4 video" do
ActiveStorage::Previewer::VideoPreviewer.new(@blob).preview do |attachable|
- assert_equal "image/png", attachable[:content_type]
- assert_equal "video.png", attachable[:filename]
+ assert_equal "image/jpeg", attachable[:content_type]
+ assert_equal "video.jpg", attachable[:filename]
image = MiniMagick::Image.read(attachable[:io])
assert_equal 640, image.width
assert_equal 480, image.height
+ assert_equal "image/jpeg", image.mime_type
end
end
end
diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb
index be31bbe858..09c2e7f99c 100644
--- a/activestorage/test/service/azure_storage_service_test.rb
+++ b/activestorage/test/service/azure_storage_service_test.rb
@@ -10,12 +10,29 @@ if SERVICE_CONFIGURATIONS[:azure]
include ActiveStorage::Service::SharedServiceTests
test "signed URL generation" do
- url = @service.url(FIXTURE_KEY, expires_in: 5.minutes,
+ url = @service.url(@key, expires_in: 5.minutes,
disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")
assert_match(/(\S+)&rscd=inline%3B\+filename%3D%22avatar\.png%22%3B\+filename\*%3DUTF-8%27%27avatar\.png&rsct=image%2Fpng/, url)
assert_match SERVICE_CONFIGURATIONS[:azure][:container], url
end
+
+ test "uploading a tempfile" do
+ begin
+ key = SecureRandom.base58(24)
+ data = "Something else entirely!"
+
+ Tempfile.open do |file|
+ file.write(data)
+ file.rewind
+ @service.upload(key, file)
+ end
+
+ assert_equal data, @service.download(key)
+ ensure
+ @service.delete(key)
+ end
+ end
end
else
puts "Skipping Azure Storage Service tests because no Azure configuration was supplied"
diff --git a/activestorage/test/service/configurator_test.rb b/activestorage/test/service/configurator_test.rb
index 1c9c5c3aa0..3ef9cf9fb6 100644
--- a/activestorage/test/service/configurator_test.rb
+++ b/activestorage/test/service/configurator_test.rb
@@ -9,6 +9,12 @@ class ActiveStorage::Service::ConfiguratorTest < ActiveSupport::TestCase
assert_equal "path", service.root
end
+ test "builds correct service instance based on lowercase service name" do
+ service = ActiveStorage::Service::Configurator.build(:foo, foo: { service: "disk", root: "path" })
+ assert_instance_of ActiveStorage::Service::DiskService, service
+ assert_equal "path", service.root
+ end
+
test "raises error when passing non-existent service name" do
assert_raise RuntimeError do
ActiveStorage::Service::Configurator.build(:bigfoot, {})
diff --git a/activestorage/test/service/disk_service_test.rb b/activestorage/test/service/disk_service_test.rb
index d7142de458..a0218bff1c 100644
--- a/activestorage/test/service/disk_service_test.rb
+++ b/activestorage/test/service/disk_service_test.rb
@@ -9,6 +9,10 @@ class ActiveStorage::Service::DiskServiceTest < ActiveSupport::TestCase
test "url generation" do
assert_match(/^https:\/\/example.com\/rails\/active_storage\/disk\/.*\/avatar\.png\?content_type=image%2Fpng&disposition=inline/,
- @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png"))
+ @service.url(@key, expires_in: 5.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png"))
+ end
+
+ test "headers_for_direct_upload generation" do
+ assert_equal({ "Content-Type" => "application/json" }, @service.headers_for_direct_upload(@key, content_type: "application/json"))
end
end
diff --git a/activestorage/test/service/gcs_service_test.rb b/activestorage/test/service/gcs_service_test.rb
index fc2d9d0fa7..2ba2f8b346 100644
--- a/activestorage/test/service/gcs_service_test.rb
+++ b/activestorage/test/service/gcs_service_test.rb
@@ -33,7 +33,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
test "signed URL generation" do
assert_match(/storage\.googleapis\.com\/.*response-content-disposition=inline.*test\.txt.*response-content-type=text%2Fplain/,
- @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain"))
+ @service.url(@key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain"))
end
test "signed URL response headers" do
@@ -44,7 +44,7 @@ if SERVICE_CONFIGURATIONS[:gcs]
url = @service.url(key, expires_in: 2.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
response = Net::HTTP.get_response(URI(url))
- assert_equal "text/plain", response.header["Content-Type"]
+ assert_equal "text/plain", response.content_type
ensure
@service.delete key
end
diff --git a/activestorage/test/service/mirror_service_test.rb b/activestorage/test/service/mirror_service_test.rb
index 87306644c5..bb502dde60 100644
--- a/activestorage/test/service/mirror_service_test.rb
+++ b/activestorage/test/service/mirror_service_test.rb
@@ -47,11 +47,11 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
end
test "deleting from all services" do
- @service.delete FIXTURE_KEY
+ @service.delete @key
- assert_not SERVICE.primary.exist?(FIXTURE_KEY)
+ assert_not SERVICE.primary.exist?(@key)
SERVICE.mirrors.each do |mirror|
- assert_not mirror.exist?(FIXTURE_KEY)
+ assert_not mirror.exist?(@key)
end
end
@@ -59,8 +59,8 @@ class ActiveStorage::Service::MirrorServiceTest < ActiveSupport::TestCase
filename = ActiveStorage::Filename.new("test.txt")
freeze_time do
- assert_equal @service.primary.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain"),
- @service.url(FIXTURE_KEY, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain")
+ assert_equal @service.primary.url(@key, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain"),
+ @service.url(@key, expires_in: 2.minutes, disposition: :inline, filename: filename, content_type: "text/plain")
end
end
end
diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb
index 7833e51122..4bfcda017f 100644
--- a/activestorage/test/service/s3_service_test.rb
+++ b/activestorage/test/service/s3_service_test.rb
@@ -32,7 +32,7 @@ if SERVICE_CONFIGURATIONS[:s3]
end
test "signed URL generation" do
- url = @service.url(FIXTURE_KEY, expires_in: 5.minutes,
+ url = @service.url(@key, expires_in: 5.minutes,
disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")
assert_match(/s3(-[-a-z0-9]+)?\.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, url)
diff --git a/activestorage/test/service/shared_service_tests.rb b/activestorage/test/service/shared_service_tests.rb
index 24debe7f47..58f189af2b 100644
--- a/activestorage/test/service/shared_service_tests.rb
+++ b/activestorage/test/service/shared_service_tests.rb
@@ -6,17 +6,17 @@ require "active_support/core_ext/securerandom"
module ActiveStorage::Service::SharedServiceTests
extend ActiveSupport::Concern
- FIXTURE_KEY = SecureRandom.base58(24)
FIXTURE_DATA = "\211PNG\r\n\032\n\000\000\000\rIHDR\000\000\000\020\000\000\000\020\001\003\000\000\000%=m\"\000\000\000\006PLTE\000\000\000\377\377\377\245\331\237\335\000\000\0003IDATx\234c\370\377\237\341\377_\206\377\237\031\016\2603\334?\314p\1772\303\315\315\f7\215\031\356\024\203\320\275\317\f\367\201R\314\f\017\300\350\377\177\000Q\206\027(\316]\233P\000\000\000\000IEND\256B`\202".dup.force_encoding(Encoding::BINARY)
included do
setup do
+ @key = SecureRandom.base58(24)
@service = self.class.const_get(:SERVICE)
- @service.upload FIXTURE_KEY, StringIO.new(FIXTURE_DATA)
+ @service.upload @key, StringIO.new(FIXTURE_DATA)
end
teardown do
- @service.delete FIXTURE_KEY
+ @service.delete @key
end
test "uploading with integrity" do
@@ -47,32 +47,61 @@ module ActiveStorage::Service::SharedServiceTests
end
test "downloading" do
- assert_equal FIXTURE_DATA, @service.download(FIXTURE_KEY)
+ assert_equal FIXTURE_DATA, @service.download(@key)
end
+ test "downloading a nonexistent file" do
+ assert_raises(ActiveStorage::FileNotFoundError) do
+ @service.download(SecureRandom.base58(24))
+ end
+ end
+
+
test "downloading in chunks" do
- chunks = []
+ key = SecureRandom.base58(24)
+ expected_chunks = [ "a" * 5.megabytes, "b" ]
+ actual_chunks = []
- @service.download(FIXTURE_KEY) do |chunk|
- chunks << chunk
+ begin
+ @service.upload key, StringIO.new(expected_chunks.join)
+
+ @service.download key do |chunk|
+ actual_chunks << chunk
+ end
+
+ assert_equal expected_chunks, actual_chunks, "Downloaded chunks did not match uploaded data"
+ ensure
+ @service.delete key
end
+ end
- assert_equal [ FIXTURE_DATA ], chunks
+ test "downloading a nonexistent file in chunks" do
+ assert_raises(ActiveStorage::FileNotFoundError) do
+ @service.download(SecureRandom.base58(24)) {}
+ end
end
+
test "downloading partially" do
- assert_equal "\x10\x00\x00", @service.download_chunk(FIXTURE_KEY, 19..21)
- assert_equal "\x10\x00\x00", @service.download_chunk(FIXTURE_KEY, 19...22)
+ assert_equal "\x10\x00\x00", @service.download_chunk(@key, 19..21)
+ assert_equal "\x10\x00\x00", @service.download_chunk(@key, 19...22)
end
+ test "partially downloading a nonexistent file" do
+ assert_raises(ActiveStorage::FileNotFoundError) do
+ @service.download_chunk(SecureRandom.base58(24), 19..21)
+ end
+ end
+
+
test "existing" do
- assert @service.exist?(FIXTURE_KEY)
- assert_not @service.exist?(FIXTURE_KEY + "nonsense")
+ assert @service.exist?(@key)
+ assert_not @service.exist?(@key + "nonsense")
end
test "deleting" do
- @service.delete FIXTURE_KEY
- assert_not @service.exist?(FIXTURE_KEY)
+ @service.delete @key
+ assert_not @service.exist?(@key)
end
test "deleting nonexistent key" do
diff --git a/activestorage/test/test_helper.rb b/activestorage/test/test_helper.rb
index 028874f374..7b7926ac79 100644
--- a/activestorage/test/test_helper.rb
+++ b/activestorage/test/test_helper.rb
@@ -7,7 +7,7 @@ require "bundler/setup"
require "active_support"
require "active_support/test_case"
require "active_support/testing/autorun"
-require "mini_magick"
+require "image_processing/mini_magick"
begin
require "byebug"
@@ -50,8 +50,8 @@ class ActiveSupport::TestCase
end
private
- def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain")
- ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type
+ def create_blob(data: "Hello world!", filename: "hello.txt", content_type: "text/plain", identify: true)
+ ActiveStorage::Blob.create_after_upload! io: StringIO.new(data), filename: filename, content_type: content_type, identify: identify
end
def create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg", metadata: nil)
@@ -75,6 +75,14 @@ class ActiveSupport::TestCase
def read_image(blob_or_variant)
MiniMagick::Image.open blob_or_variant.service.send(:path_for, blob_or_variant.key)
end
+
+ def extract_metadata_from(blob)
+ blob.tap(&:analyze).metadata
+ end
+
+ def fixture_file_upload(filename)
+ Rack::Test::UploadedFile.new file_fixture(filename).to_s
+ end
end
require "global_id"
@@ -82,9 +90,15 @@ GlobalID.app = "ActiveStorageExampleApp"
ActiveRecord::Base.send :include, GlobalID::Identification
class User < ActiveRecord::Base
+ validates :name, presence: true
+
has_one_attached :avatar
has_one_attached :cover_photo, dependent: false
has_many_attached :highlights
has_many_attached :vlogs, dependent: false
end
+
+class Group < ActiveRecord::Base
+ has_one_attached :avatar
+end
diff --git a/activestorage/webpack.config.js b/activestorage/webpack.config.js
deleted file mode 100644
index 3a50eef470..0000000000
--- a/activestorage/webpack.config.js
+++ /dev/null
@@ -1,26 +0,0 @@
-const path = require("path")
-
-module.exports = {
- entry: {
- "activestorage": path.resolve(__dirname, "app/javascript/activestorage/index.js"),
- },
-
- output: {
- filename: "[name].js",
- path: path.resolve(__dirname, "app/assets/javascripts"),
- library: "ActiveStorage",
- libraryTarget: "umd"
- },
-
- module: {
- rules: [
- {
- test: /\.js$/,
- exclude: /node_modules/,
- use: {
- loader: "babel-loader"
- }
- }
- ]
- }
-}
diff --git a/activestorage/yarn.lock b/activestorage/yarn.lock
index 41742be201..44eae3c5b1 100644
--- a/activestorage/yarn.lock
+++ b/activestorage/yarn.lock
@@ -2,15 +2,13 @@
# yarn lockfile v1
-abbrev@1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f"
+"@types/estree@0.0.38":
+ version "0.0.38"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.38.tgz#c1be40aa933723c608820a99a373a16d215a1ca2"
-acorn-dynamic-import@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz#c752bd210bef679501b6c6cb7fc84f8f47158cc4"
- dependencies:
- acorn "^4.0.3"
+"@types/node@*":
+ version "9.6.6"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-9.6.6.tgz#439b91f9caf3983cad2eef1e11f6bedcbf9431d2"
acorn-jsx@^3.0.0:
version "3.0.1"
@@ -22,11 +20,7 @@ acorn@^3.0.4:
version "3.3.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a"
-acorn@^4.0.3:
- version "4.0.13"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787"
-
-acorn@^5.0.0, acorn@^5.0.1:
+acorn@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.1.tgz#53fe161111f912ab999ee887a90a0bc52822fd75"
@@ -34,18 +28,14 @@ ajv-keywords@^1.0.0:
version "1.5.1"
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c"
-ajv-keywords@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0"
-
-ajv@^4.7.0, ajv@^4.9.1:
+ajv@^4.7.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536"
dependencies:
co "^4.6.0"
json-stable-stringify "^1.0.1"
-ajv@^5.1.5, ajv@^5.2.0:
+ajv@^5.2.0:
version "5.2.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39"
dependencies:
@@ -54,14 +44,6 @@ ajv@^5.1.5, ajv@^5.2.0:
json-schema-traverse "^0.3.0"
json-stable-stringify "^1.0.1"
-align-text@^0.1.1, align-text@^0.1.3:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
- dependencies:
- kind-of "^3.0.2"
- longest "^1.0.1"
- repeat-string "^1.5.2"
-
ansi-escapes@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b"
@@ -84,24 +66,6 @@ ansi-styles@^3.1.0:
dependencies:
color-convert "^1.9.0"
-anymatch@^1.3.0:
- version "1.3.0"
- resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507"
- dependencies:
- arrify "^1.0.0"
- micromatch "^2.1.5"
-
-aproba@^1.0.3:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1"
-
-are-we-there-yet@~1.1.2:
- version "1.1.4"
- resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d"
- dependencies:
- delegates "^1.0.0"
- readable-stream "^2.0.6"
-
argparse@^1.0.7:
version "1.0.9"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
@@ -136,54 +100,6 @@ arrify@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d"
-asn1.js@^4.0.0:
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.9.1.tgz#48ba240b45a9280e94748990ba597d216617fd40"
- dependencies:
- bn.js "^4.0.0"
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
-
-asn1@~0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86"
-
-assert-plus@1.0.0, assert-plus@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
-
-assert-plus@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234"
-
-assert@^1.1.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91"
- dependencies:
- util "0.10.3"
-
-async-each@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d"
-
-async@^2.1.2:
- version "2.5.0"
- resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d"
- dependencies:
- lodash "^4.14.0"
-
-asynckit@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
-
-aws-sign2@~0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f"
-
-aws4@^1.2.1:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
-
babel-code-frame@^6.22.0:
version "6.22.0"
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4"
@@ -330,14 +246,6 @@ babel-helpers@^6.24.1:
babel-runtime "^6.22.0"
babel-template "^6.24.1"
-babel-loader@^7.1.1:
- version "7.1.1"
- resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-7.1.1.tgz#b87134c8b12e3e4c2a94e0546085bc680a2b8488"
- dependencies:
- find-cache-dir "^1.0.0"
- loader-utils "^1.0.2"
- mkdirp "^0.5.1"
-
babel-messages@^6.23.0:
version "6.23.0"
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
@@ -350,6 +258,12 @@ babel-plugin-check-es2015-constants@^6.22.0:
dependencies:
babel-runtime "^6.22.0"
+babel-plugin-external-helpers@^6.22.0:
+ version "6.22.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-external-helpers/-/babel-plugin-external-helpers-6.22.0.tgz#2285f48b02bd5dede85175caf8c62e86adccefa1"
+ dependencies:
+ babel-runtime "^6.22.0"
+
babel-plugin-syntax-async-functions@^6.8.0:
version "6.13.0"
resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
@@ -654,40 +568,6 @@ balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
-base64-js@^1.0.2:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886"
-
-bcrypt-pbkdf@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d"
- dependencies:
- tweetnacl "^0.14.3"
-
-big.js@^3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978"
-
-binary-extensions@^1.0.0:
- version "1.9.0"
- resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.9.0.tgz#66506c16ce6f4d6928a5b3cd6a33ca41e941e37b"
-
-block-stream@*:
- version "0.0.9"
- resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
- dependencies:
- inherits "~2.0.0"
-
-bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
- version "4.11.7"
- resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.7.tgz#ddb048e50d9482790094c13eb3fcfc833ce7ab46"
-
-boom@2.x.x:
- version "2.10.1"
- resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f"
- dependencies:
- hoek "2.x.x"
-
brace-expansion@^1.1.7:
version "1.1.8"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
@@ -703,61 +583,6 @@ braces@^1.8.2:
preserve "^0.2.0"
repeat-element "^1.1.2"
-brorand@^1.0.1:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
-
-browserify-aes@^1.0.0, browserify-aes@^1.0.4:
- version "1.0.6"
- resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a"
- dependencies:
- buffer-xor "^1.0.2"
- cipher-base "^1.0.0"
- create-hash "^1.1.0"
- evp_bytestokey "^1.0.0"
- inherits "^2.0.1"
-
-browserify-cipher@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.0.tgz#9988244874bf5ed4e28da95666dcd66ac8fc363a"
- dependencies:
- browserify-aes "^1.0.4"
- browserify-des "^1.0.0"
- evp_bytestokey "^1.0.0"
-
-browserify-des@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.0.tgz#daa277717470922ed2fe18594118a175439721dd"
- dependencies:
- cipher-base "^1.0.1"
- des.js "^1.0.0"
- inherits "^2.0.1"
-
-browserify-rsa@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524"
- dependencies:
- bn.js "^4.1.0"
- randombytes "^2.0.1"
-
-browserify-sign@^4.0.0:
- version "4.0.4"
- resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298"
- dependencies:
- bn.js "^4.1.1"
- browserify-rsa "^4.0.0"
- create-hash "^1.1.0"
- create-hmac "^1.1.2"
- elliptic "^6.0.0"
- inherits "^2.0.1"
- parse-asn1 "^5.0.0"
-
-browserify-zlib@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.1.4.tgz#bb35f8a519f600e0fa6b8485241c979d0141fb2d"
- dependencies:
- pako "~0.2.0"
-
browserslist@^2.1.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.2.2.tgz#e9b4618b8a01c193f9786beea09f6fd10dbe31c3"
@@ -765,25 +590,13 @@ browserslist@^2.1.2:
caniuse-lite "^1.0.30000704"
electron-to-chromium "^1.3.16"
-buffer-xor@^1.0.2:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
-
-buffer@^4.3.0:
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298"
- dependencies:
- base64-js "^1.0.2"
- ieee754 "^1.1.4"
- isarray "^1.0.0"
-
builtin-modules@^1.0.0, builtin-modules@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
-builtin-status-codes@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
+builtin-modules@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-2.0.0.tgz#60b7ef5ae6546bd7deefa74b08b62a43a232648e"
caller-path@^0.1.0:
version "0.1.0"
@@ -795,29 +608,10 @@ callsites@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca"
-camelcase@^1.0.2:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
-
-camelcase@^4.1.0:
- version "4.1.0"
- resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
-
caniuse-lite@^1.0.30000704:
version "1.0.30000706"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000706.tgz#bc59abc41ba7d4a3634dda95befded6114e1f24e"
-caseless@~0.12.0:
- version "0.12.0"
- resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
-
-center-align@^0.1.1:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
- dependencies:
- align-text "^0.1.3"
- lazy-cache "^1.0.3"
-
chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -836,28 +630,6 @@ chalk@^2.0.0:
escape-string-regexp "^1.0.5"
supports-color "^4.0.0"
-chokidar@^1.7.0:
- version "1.7.0"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468"
- dependencies:
- anymatch "^1.3.0"
- async-each "^1.0.0"
- glob-parent "^2.0.0"
- inherits "^2.0.1"
- is-binary-path "^1.0.0"
- is-glob "^2.0.0"
- path-is-absolute "^1.0.0"
- readdirp "^2.0.0"
- optionalDependencies:
- fsevents "^1.0.0"
-
-cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
- dependencies:
- inherits "^2.0.1"
- safe-buffer "^5.0.1"
-
circular-json@^0.3.1:
version "0.3.3"
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
@@ -872,30 +644,10 @@ cli-width@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
-cliui@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
- dependencies:
- center-align "^0.1.1"
- right-align "^0.1.1"
- wordwrap "0.0.2"
-
-cliui@^3.2.0:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
- dependencies:
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
- wrap-ansi "^2.0.0"
-
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
-code-point-at@^1.0.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
-
color-convert@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a"
@@ -906,15 +658,9 @@ color-name@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
-combined-stream@^1.0.5, combined-stream@~1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
- dependencies:
- delayed-stream "~1.0.0"
-
-commondir@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
+commander@~2.13.0:
+ version "2.13.0"
+ resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c"
concat-map@0.0.1:
version "0.0.1"
@@ -928,20 +674,6 @@ concat-stream@^1.6.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
-console-browserify@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10"
- dependencies:
- date-now "^0.1.4"
-
-console-control-strings@^1.0.0, console-control-strings@~1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
-
-constants-browserify@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
-
contains-path@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a"
@@ -958,34 +690,7 @@ core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
-create-ecdh@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.0.tgz#888c723596cdf7612f6498233eebd7a35301737d"
- dependencies:
- bn.js "^4.1.0"
- elliptic "^6.0.0"
-
-create-hash@^1.1.0, create-hash@^1.1.1, create-hash@^1.1.2:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd"
- dependencies:
- cipher-base "^1.0.1"
- inherits "^2.0.1"
- ripemd160 "^2.0.0"
- sha.js "^2.4.0"
-
-create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4:
- version "1.1.6"
- resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06"
- dependencies:
- cipher-base "^1.0.3"
- create-hash "^1.1.0"
- inherits "^2.0.1"
- ripemd160 "^2.0.0"
- safe-buffer "^5.0.1"
- sha.js "^2.4.8"
-
-cross-spawn@^5.0.1, cross-spawn@^5.1.0:
+cross-spawn@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
dependencies:
@@ -993,57 +698,12 @@ cross-spawn@^5.0.1, cross-spawn@^5.1.0:
shebang-command "^1.2.0"
which "^1.2.9"
-cryptiles@2.x.x:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8"
- dependencies:
- boom "2.x.x"
-
-crypto-browserify@^3.11.0:
- version "3.11.1"
- resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f"
- dependencies:
- browserify-cipher "^1.0.0"
- browserify-sign "^4.0.0"
- create-ecdh "^4.0.0"
- create-hash "^1.1.0"
- create-hmac "^1.1.0"
- diffie-hellman "^5.0.0"
- inherits "^2.0.1"
- pbkdf2 "^3.0.3"
- public-encrypt "^4.0.0"
- randombytes "^2.0.0"
-
-d@1:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f"
- dependencies:
- es5-ext "^0.10.9"
-
-dashdash@^1.12.0:
- version "1.14.1"
- resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
- dependencies:
- assert-plus "^1.0.0"
-
-date-now@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
-
debug@^2.1.1, debug@^2.2.0, debug@^2.6.8:
version "2.6.8"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc"
dependencies:
ms "2.0.0"
-decamelize@^1.0.0, decamelize@^1.1.1:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
-
-deep-extend@~0.4.0:
- version "0.4.2"
- resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f"
-
deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -1060,35 +720,12 @@ del@^2.0.2:
pinkie-promise "^2.0.0"
rimraf "^2.2.8"
-delayed-stream@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
-
-delegates@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
-
-des.js@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc"
- dependencies:
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
-
detect-indent@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208"
dependencies:
repeating "^2.0.0"
-diffie-hellman@^5.0.0:
- version "5.0.2"
- resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
- dependencies:
- bn.js "^4.1.0"
- miller-rabin "^4.0.0"
- randombytes "^2.0.0"
-
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -1103,122 +740,20 @@ doctrine@^2.0.0:
esutils "^2.0.2"
isarray "^1.0.0"
-domain-browser@^1.1.1:
- version "1.1.7"
- resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.1.7.tgz#867aa4b093faa05f1de08c06f4d7b21fdf8698bc"
-
-ecc-jsbn@~0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505"
- dependencies:
- jsbn "~0.1.0"
-
electron-to-chromium@^1.3.16:
version "1.3.16"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.16.tgz#d0e026735754770901ae301a21664cba45d92f7d"
-elliptic@^6.0.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df"
- dependencies:
- bn.js "^4.4.0"
- brorand "^1.0.1"
- hash.js "^1.0.0"
- hmac-drbg "^1.0.0"
- inherits "^2.0.1"
- minimalistic-assert "^1.0.0"
- minimalistic-crypto-utils "^1.0.0"
-
-emojis-list@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389"
-
-enhanced-resolve@^3.4.0:
- version "3.4.1"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e"
- dependencies:
- graceful-fs "^4.1.2"
- memory-fs "^0.4.0"
- object-assign "^4.0.1"
- tapable "^0.2.7"
-
-errno@^0.1.3:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d"
- dependencies:
- prr "~0.0.0"
-
error-ex@^1.2.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc"
dependencies:
is-arrayish "^0.2.1"
-es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14:
- version "0.10.24"
- resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.24.tgz#a55877c9924bc0c8d9bd3c2cbe17495ac1709b14"
- dependencies:
- es6-iterator "2"
- es6-symbol "~3.1"
-
-es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512"
- dependencies:
- d "1"
- es5-ext "^0.10.14"
- es6-symbol "^3.1"
-
-es6-map@^0.1.3:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
- dependencies:
- d "1"
- es5-ext "~0.10.14"
- es6-iterator "~2.0.1"
- es6-set "~0.1.5"
- es6-symbol "~3.1.1"
- event-emitter "~0.3.5"
-
-es6-set@~0.1.5:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
- dependencies:
- d "1"
- es5-ext "~0.10.14"
- es6-iterator "~2.0.1"
- es6-symbol "3.1.1"
- event-emitter "~0.3.5"
-
-es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1:
- version "3.1.1"
- resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
- dependencies:
- d "1"
- es5-ext "~0.10.14"
-
-es6-weak-map@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f"
- dependencies:
- d "1"
- es5-ext "^0.10.14"
- es6-iterator "^2.0.1"
- es6-symbol "^3.1.1"
-
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
-escope@^3.6.0:
- version "3.6.0"
- resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3"
- dependencies:
- es6-map "^0.1.3"
- es6-weak-map "^2.0.1"
- esrecurse "^4.1.0"
- estraverse "^4.1.1"
-
eslint-import-resolver-node@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.1.tgz#4422574cde66a9a7b099938ee4d508a199e0e3cc"
@@ -1324,38 +859,21 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
-esutils@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
-
-event-emitter@~0.3.5:
- version "0.3.5"
- resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
- dependencies:
- d "1"
- es5-ext "~0.10.14"
+estree-walker@^0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e"
-events@^1.0.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924"
+estree-walker@^0.3.0:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.3.1.tgz#e6b1a51cf7292524e7237c312e5fe6660c1ce1aa"
-evp_bytestokey@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53"
- dependencies:
- create-hash "^1.1.1"
+estree-walker@^0.5.1:
+ version "0.5.1"
+ resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.5.1.tgz#64fc375053abc6f57d73e9bd2f004644ad3c5854"
-execa@^0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
- dependencies:
- cross-spawn "^5.0.1"
- get-stream "^3.0.0"
- is-stream "^1.1.0"
- npm-run-path "^2.0.0"
- p-finally "^1.0.0"
- signal-exit "^3.0.0"
- strip-eof "^1.0.0"
+esutils@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
expand-brackets@^0.1.4:
version "0.1.5"
@@ -1369,10 +887,6 @@ expand-range@^1.8.1:
dependencies:
fill-range "^2.1.0"
-extend@~3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444"
-
external-editor@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972"
@@ -1387,10 +901,6 @@ extglob@^0.3.1:
dependencies:
is-extglob "^1.0.0"
-extsprintf@1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550"
-
fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
@@ -1426,14 +936,6 @@ fill-range@^2.1.0:
repeat-element "^1.1.2"
repeat-string "^1.5.2"
-find-cache-dir@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f"
- dependencies:
- commondir "^1.0.1"
- make-dir "^1.0.0"
- pkg-dir "^2.0.0"
-
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
@@ -1441,7 +943,7 @@ find-up@^1.0.0:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
-find-up@^2.0.0, find-up@^2.1.0:
+find-up@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
dependencies:
@@ -1466,46 +968,10 @@ for-own@^0.1.4:
dependencies:
for-in "^1.0.1"
-forever-agent@~0.6.1:
- version "0.6.1"
- resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
-
-form-data@~2.1.1:
- version "2.1.4"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1"
- dependencies:
- asynckit "^0.4.0"
- combined-stream "^1.0.5"
- mime-types "^2.1.12"
-
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
-fsevents@^1.0.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4"
- dependencies:
- nan "^2.3.0"
- node-pre-gyp "^0.6.36"
-
-fstream-ignore@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105"
- dependencies:
- fstream "^1.0.0"
- inherits "2"
- minimatch "^3.0.0"
-
-fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2:
- version "1.0.11"
- resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171"
- dependencies:
- graceful-fs "^4.1.2"
- inherits "~2.0.0"
- mkdirp ">=0.5 0"
- rimraf "2"
-
function-bind@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771"
@@ -1514,33 +980,6 @@ functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
-gauge@~2.7.3:
- version "2.7.4"
- resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
- dependencies:
- aproba "^1.0.3"
- console-control-strings "^1.0.0"
- has-unicode "^2.0.0"
- object-assign "^4.1.0"
- signal-exit "^3.0.0"
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
- wide-align "^1.1.0"
-
-get-caller-file@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
-
-get-stream@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
-
-getpass@^0.1.1:
- version "0.1.7"
- resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
- dependencies:
- assert-plus "^1.0.0"
-
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -1584,17 +1023,6 @@ graceful-fs@^4.1.2:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
-har-schema@^1.0.5:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e"
-
-har-validator@~4.2.1:
- version "4.2.1"
- resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a"
- dependencies:
- ajv "^4.9.1"
- har-schema "^1.0.5"
-
has-ansi@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
@@ -1605,50 +1033,12 @@ has-flag@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51"
-has-unicode@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
-
has@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
dependencies:
function-bind "^1.0.2"
-hash-base@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1"
- dependencies:
- inherits "^2.0.1"
-
-hash.js@^1.0.0, hash.js@^1.0.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846"
- dependencies:
- inherits "^2.0.3"
- minimalistic-assert "^1.0.0"
-
-hawk@~3.1.3:
- version "3.1.3"
- resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4"
- dependencies:
- boom "2.x.x"
- cryptiles "2.x.x"
- hoek "2.x.x"
- sntp "1.x.x"
-
-hmac-drbg@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
- dependencies:
- hash.js "^1.0.3"
- minimalistic-assert "^1.0.0"
- minimalistic-crypto-utils "^1.0.1"
-
-hoek@2.x.x:
- version "2.16.3"
- resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
-
home-or-tmp@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8"
@@ -1660,26 +1050,10 @@ hosted-git-info@^2.1.4:
version "2.5.0"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
-http-signature@~1.1.0:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf"
- dependencies:
- assert-plus "^0.2.0"
- jsprim "^1.2.2"
- sshpk "^1.7.0"
-
-https-browserify@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-0.0.1.tgz#3f91365cabe60b77ed0ebba24b454e3e09d95a82"
-
iconv-lite@^0.4.17:
version "0.4.18"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2"
-ieee754@^1.1.4:
- version "1.1.8"
- resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"
-
ignore@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d"
@@ -1688,10 +1062,6 @@ imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
-indexof@0.0.1:
- version "0.0.1"
- resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d"
-
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
@@ -1699,18 +1069,10 @@ inflight@^1.0.4:
once "^1.3.0"
wrappy "1"
-inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3:
+inherits@2, inherits@^2.0.3, inherits@~2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
-inherits@2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
-
-ini@~1.3.0:
- version "1.3.4"
- resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e"
-
inquirer@^3.0.6:
version "3.2.1"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.1.tgz#06ceb0f540f45ca548c17d6840959878265fa175"
@@ -1730,30 +1092,16 @@ inquirer@^3.0.6:
strip-ansi "^4.0.0"
through "^2.3.6"
-interpret@^1.0.0:
- version "1.0.3"
- resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90"
-
invariant@^2.2.0, invariant@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360"
dependencies:
loose-envify "^1.0.0"
-invert-kv@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
-
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-is-binary-path@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898"
- dependencies:
- binary-extensions "^1.0.0"
-
is-buffer@^1.1.5:
version "1.1.5"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc"
@@ -1788,12 +1136,6 @@ is-finite@^1.0.0:
dependencies:
number-is-nan "^1.0.0"
-is-fullwidth-code-point@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
- dependencies:
- number-is-nan "^1.0.0"
-
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
@@ -1804,6 +1146,10 @@ is-glob@^2.0.0, is-glob@^2.0.1:
dependencies:
is-extglob "^1.0.0"
+is-module@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591"
+
is-number@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f"
@@ -1850,14 +1196,6 @@ is-resolvable@^1.0.0:
dependencies:
tryit "^1.0.1"
-is-stream@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
-
-is-typedarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
-
isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
@@ -1872,10 +1210,6 @@ isobject@^2.0.0:
dependencies:
isarray "1.0.0"
-isstream@~0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
-
js-tokens@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
@@ -1887,10 +1221,6 @@ js-yaml@^3.8.4:
argparse "^1.0.7"
esprima "^4.0.0"
-jsbn@~0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
-
jschardet@^1.4.2:
version "1.5.0"
resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.0.tgz#a61f310306a5a71188e1b1acd08add3cfbb08b1e"
@@ -1903,29 +1233,17 @@ jsesc@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
-json-loader@^0.5.4:
- version "0.5.7"
- resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d"
-
json-schema-traverse@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
-json-schema@0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
-
json-stable-stringify@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af"
dependencies:
jsonify "~0.0.0"
-json-stringify-safe@~5.0.1:
- version "5.0.1"
- resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
-
-json5@^0.5.0, json5@^0.5.1:
+json5@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
@@ -1933,15 +1251,6 @@ jsonify@~0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73"
-jsprim@^1.2.2:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918"
- dependencies:
- assert-plus "1.0.0"
- extsprintf "1.0.2"
- json-schema "0.2.3"
- verror "1.3.6"
-
kind-of@^3.0.2:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -1954,16 +1263,6 @@ kind-of@^4.0.0:
dependencies:
is-buffer "^1.1.5"
-lazy-cache@^1.0.3:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
-
-lcid@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
- dependencies:
- invert-kv "^1.0.0"
-
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -1980,18 +1279,6 @@ load-json-file@^2.0.0:
pify "^2.0.0"
strip-bom "^3.0.0"
-loader-runner@^2.3.0:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
-
-loader-utils@^1.0.2, loader-utils@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
- dependencies:
- big.js "^3.1.3"
- emojis-list "^2.0.0"
- json5 "^0.5.0"
-
locate-path@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"
@@ -2003,14 +1290,10 @@ lodash.cond@^4.3.0:
version "4.5.2"
resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5"
-lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
+lodash@^4.0.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
-longest@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
-
loose-envify@^1.0.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848"
@@ -2024,26 +1307,13 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
-make-dir@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.0.0.tgz#97a011751e91dd87cfadef58832ebb04936de978"
- dependencies:
- pify "^2.3.0"
-
-mem@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
- dependencies:
- mimic-fn "^1.0.0"
-
-memory-fs@^0.4.0, memory-fs@~0.4.1:
- version "0.4.1"
- resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
+magic-string@^0.22.4:
+ version "0.22.5"
+ resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.22.5.tgz#8e9cf5afddf44385c1da5bc2a6a0dbd10b03657e"
dependencies:
- errno "^0.1.3"
- readable-stream "^2.0.1"
+ vlq "^0.2.2"
-micromatch@^2.1.5:
+micromatch@^2.3.11:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
dependencies:
@@ -2061,36 +1331,11 @@ micromatch@^2.1.5:
parse-glob "^3.0.4"
regex-cache "^0.4.2"
-miller-rabin@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.0.tgz#4a62fb1d42933c05583982f4c716f6fb9e6c6d3d"
- dependencies:
- bn.js "^4.0.0"
- brorand "^1.0.1"
-
-mime-db@~1.29.0:
- version "1.29.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878"
-
-mime-types@^2.1.12, mime-types@~2.1.7:
- version "2.1.16"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23"
- dependencies:
- mime-db "~1.29.0"
-
mimic-fn@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
-minimalistic-assert@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz#702be2dda6b37f4836bcb3f5db56641b64a1d3d3"
-
-minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
-
-minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
+minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
dependencies:
@@ -2100,11 +1345,7 @@ minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
-minimist@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
-
-"mkdirp@>=0.5 0", mkdirp@^0.5.1, mkdirp@~0.5.0:
+mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
dependencies:
@@ -2118,63 +1359,10 @@ mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
-nan@^2.3.0:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45"
-
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
-node-libs-browser@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.0.0.tgz#a3a59ec97024985b46e958379646f96c4b616646"
- dependencies:
- assert "^1.1.1"
- browserify-zlib "^0.1.4"
- buffer "^4.3.0"
- console-browserify "^1.1.0"
- constants-browserify "^1.0.0"
- crypto-browserify "^3.11.0"
- domain-browser "^1.1.1"
- events "^1.0.0"
- https-browserify "0.0.1"
- os-browserify "^0.2.0"
- path-browserify "0.0.0"
- process "^0.11.0"
- punycode "^1.2.4"
- querystring-es3 "^0.2.0"
- readable-stream "^2.0.5"
- stream-browserify "^2.0.1"
- stream-http "^2.3.1"
- string_decoder "^0.10.25"
- timers-browserify "^2.0.2"
- tty-browserify "0.0.0"
- url "^0.11.0"
- util "^0.10.3"
- vm-browserify "0.0.4"
-
-node-pre-gyp@^0.6.36:
- version "0.6.36"
- resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786"
- dependencies:
- mkdirp "^0.5.1"
- nopt "^4.0.1"
- npmlog "^4.0.2"
- rc "^1.1.7"
- request "^2.81.0"
- rimraf "^2.6.1"
- semver "^5.3.0"
- tar "^2.2.1"
- tar-pack "^3.4.0"
-
-nopt@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
- dependencies:
- abbrev "1"
- osenv "^0.1.4"
-
normalize-package-data@^2.3.2:
version "2.4.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
@@ -2190,30 +1378,11 @@ normalize-path@^2.0.1:
dependencies:
remove-trailing-separator "^1.0.1"
-npm-run-path@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
- dependencies:
- path-key "^2.0.0"
-
-npmlog@^4.0.2:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
- dependencies:
- are-we-there-yet "~1.1.2"
- console-control-strings "~1.1.0"
- gauge "~2.7.3"
- set-blocking "~2.0.0"
-
number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
-oauth-sign@~0.8.1:
- version "0.8.2"
- resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
-
-object-assign@^4.0.1, object-assign@^4.1.0:
+object-assign@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -2224,7 +1393,7 @@ object.omit@^2.0.0:
for-own "^0.1.4"
is-extendable "^0.1.1"
-once@^1.3.0, once@^1.3.3:
+once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
dependencies:
@@ -2247,37 +1416,14 @@ optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
-os-browserify@^0.2.0:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.2.1.tgz#63fc4ccee5d2d7763d26bbf8601078e6c2e0044f"
-
os-homedir@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
-os-locale@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
- dependencies:
- execa "^0.7.0"
- lcid "^1.0.0"
- mem "^1.1.0"
-
-os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
+os-tmpdir@^1.0.1, os-tmpdir@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
-osenv@^0.1.4:
- version "0.1.4"
- resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644"
- dependencies:
- os-homedir "^1.0.0"
- os-tmpdir "^1.0.0"
-
-p-finally@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
-
p-limit@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc"
@@ -2288,20 +1434,6 @@ p-locate@^2.0.0:
dependencies:
p-limit "^1.1.0"
-pako@~0.2.0:
- version "0.2.9"
- resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75"
-
-parse-asn1@^5.0.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.0.tgz#37c4f9b7ed3ab65c74817b5f2480937fbf97c712"
- dependencies:
- asn1.js "^4.0.0"
- browserify-aes "^1.0.0"
- create-hash "^1.1.0"
- evp_bytestokey "^1.0.0"
- pbkdf2 "^3.0.3"
-
parse-glob@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c"
@@ -2317,10 +1449,6 @@ parse-json@^2.2.0:
dependencies:
error-ex "^1.2.0"
-path-browserify@0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a"
-
path-exists@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
@@ -2339,10 +1467,6 @@ path-is-inside@^1.0.1, path-is-inside@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
-path-key@^2.0.0:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
-
path-parse@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
@@ -2353,21 +1477,7 @@ path-type@^2.0.0:
dependencies:
pify "^2.0.0"
-pbkdf2@^3.0.3:
- version "3.0.12"
- resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.12.tgz#be36785c5067ea48d806ff923288c5f750b6b8a2"
- dependencies:
- create-hash "^1.1.2"
- create-hmac "^1.1.4"
- ripemd160 "^2.0.1"
- safe-buffer "^5.0.1"
- sha.js "^2.4.8"
-
-performance-now@^0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5"
-
-pify@^2.0.0, pify@^2.3.0:
+pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -2387,12 +1497,6 @@ pkg-dir@^1.0.0:
dependencies:
find-up "^1.0.0"
-pkg-dir@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
- dependencies:
- find-up "^2.1.0"
-
pluralize@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762"
@@ -2413,52 +1517,14 @@ process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
-process@^0.11.0:
- version "0.11.10"
- resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
-
progress@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f"
-prr@~0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a"
-
pseudomap@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
-public-encrypt@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.0.tgz#39f699f3a46560dd5ebacbca693caf7c65c18cc6"
- dependencies:
- bn.js "^4.1.0"
- browserify-rsa "^4.0.0"
- create-hash "^1.1.0"
- parse-asn1 "^5.0.0"
- randombytes "^2.0.1"
-
-punycode@1.3.2:
- version "1.3.2"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
-
-punycode@^1.2.4, punycode@^1.4.1:
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
-
-qs@~6.4.0:
- version "6.4.0"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233"
-
-querystring-es3@^0.2.0:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73"
-
-querystring@0.2.0:
- version "0.2.0"
- resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620"
-
randomatic@^1.1.3:
version "1.1.7"
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
@@ -2466,21 +1532,6 @@ randomatic@^1.1.3:
is-number "^3.0.0"
kind-of "^4.0.0"
-randombytes@^2.0.0, randombytes@^2.0.1:
- version "2.0.5"
- resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79"
- dependencies:
- safe-buffer "^5.1.0"
-
-rc@^1.1.7:
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95"
- dependencies:
- deep-extend "~0.4.0"
- ini "~1.3.0"
- minimist "^1.2.0"
- strip-json-comments "~2.0.1"
-
read-pkg-up@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be"
@@ -2496,7 +1547,7 @@ read-pkg@^2.0.0:
normalize-package-data "^2.3.2"
path-type "^2.0.0"
-readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.2, readable-stream@^2.2.6:
+readable-stream@^2.2.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
dependencies:
@@ -2508,15 +1559,6 @@ readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable
string_decoder "~1.0.3"
util-deprecate "~1.0.1"
-readdirp@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
- dependencies:
- graceful-fs "^4.1.2"
- minimatch "^3.0.2"
- readable-stream "^2.0.2"
- set-immediate-shim "^1.0.1"
-
regenerate@^1.2.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260"
@@ -2576,41 +1618,6 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
-request@^2.81.0:
- version "2.81.0"
- resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0"
- dependencies:
- aws-sign2 "~0.6.0"
- aws4 "^1.2.1"
- caseless "~0.12.0"
- combined-stream "~1.0.5"
- extend "~3.0.0"
- forever-agent "~0.6.1"
- form-data "~2.1.1"
- har-validator "~4.2.1"
- hawk "~3.1.3"
- http-signature "~1.1.0"
- is-typedarray "~1.0.0"
- isstream "~0.1.2"
- json-stringify-safe "~5.0.1"
- mime-types "~2.1.7"
- oauth-sign "~0.8.1"
- performance-now "^0.2.0"
- qs "~6.4.0"
- safe-buffer "^5.0.1"
- stringstream "~0.0.4"
- tough-cookie "~2.3.0"
- tunnel-agent "^0.6.0"
- uuid "^3.0.0"
-
-require-directory@^2.1.1:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
-
-require-main-filename@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
-
require-uncached@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3"
@@ -2622,6 +1629,12 @@ resolve-from@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226"
+resolve@^1.1.6, resolve@^1.5.0:
+ version "1.7.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3"
+ dependencies:
+ path-parse "^1.0.5"
+
resolve@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86"
@@ -2635,24 +1648,61 @@ restore-cursor@^2.0.0:
onetime "^2.0.0"
signal-exit "^3.0.2"
-right-align@^0.1.1:
- version "0.1.3"
- resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
- dependencies:
- align-text "^0.1.1"
-
-rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1:
+rimraf@^2.2.8:
version "2.6.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
dependencies:
glob "^7.0.5"
-ripemd160@^2.0.0, ripemd160@^2.0.1:
+rollup-plugin-babel@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-babel/-/rollup-plugin-babel-3.0.4.tgz#41b3e762fe64450dd61da3105a2cf7ad76be4edc"
+ dependencies:
+ rollup-pluginutils "^1.5.0"
+
+rollup-plugin-commonjs@^9.1.0:
+ version "9.1.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.1.0.tgz#468341aab32499123ee9a04b22f51d9bf26fdd94"
+ dependencies:
+ estree-walker "^0.5.1"
+ magic-string "^0.22.4"
+ resolve "^1.5.0"
+ rollup-pluginutils "^2.0.1"
+
+rollup-plugin-node-resolve@^3.3.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.3.0.tgz#c26d110a36812cbefa7ce117cadcd3439aa1c713"
+ dependencies:
+ builtin-modules "^2.0.0"
+ is-module "^1.0.0"
+ resolve "^1.1.6"
+
+rollup-plugin-uglify@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/rollup-plugin-uglify/-/rollup-plugin-uglify-3.0.0.tgz#a34eca24617709c6bf1778e9653baafa06099b86"
+ dependencies:
+ uglify-es "^3.3.7"
+
+rollup-pluginutils@^1.5.0:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz#1e156e778f94b7255bfa1b3d0178be8f5c552408"
+ dependencies:
+ estree-walker "^0.2.1"
+ minimatch "^3.0.2"
+
+rollup-pluginutils@^2.0.1:
version "2.0.1"
- resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7"
+ resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz#7ec95b3573f6543a46a6461bd9a7c544525d0fc0"
+ dependencies:
+ estree-walker "^0.3.0"
+ micromatch "^2.3.11"
+
+rollup@^0.58.2:
+ version "0.58.2"
+ resolved "https://registry.yarnpkg.com/rollup/-/rollup-0.58.2.tgz#2feddea8c0c022f3e74b35c48e3c21b3433803ce"
dependencies:
- hash-base "^2.0.0"
- inherits "^2.0.1"
+ "@types/estree" "0.0.38"
+ "@types/node" "*"
run-async@^2.2.0:
version "2.3.0"
@@ -2670,7 +1720,7 @@ rx-lite@*, rx-lite@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444"
-safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
@@ -2678,24 +1728,6 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
version "5.4.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e"
-set-blocking@^2.0.0, set-blocking@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
-
-set-immediate-shim@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61"
-
-setimmediate@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
-
-sha.js@^2.4.0, sha.js@^2.4.8:
- version "2.4.8"
- resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f"
- dependencies:
- inherits "^2.0.1"
-
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -2706,7 +1738,7 @@ shebang-regex@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
-signal-exit@^3.0.0, signal-exit@^3.0.2:
+signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
@@ -2718,26 +1750,20 @@ slice-ansi@0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
-sntp@1.x.x:
- version "1.0.9"
- resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198"
- dependencies:
- hoek "2.x.x"
-
-source-list-map@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
-
source-map-support@^0.4.2:
version "0.4.15"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1"
dependencies:
source-map "^0.5.6"
-source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3:
+source-map@^0.5.0, source-map@^0.5.6:
version "0.5.6"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
+source-map@~0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+
spark-md5@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.0.tgz#3722227c54e2faf24b1dc6d933cc144e6f71bfef"
@@ -2760,45 +1786,6 @@ sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
-sshpk@^1.7.0:
- version "1.13.1"
- resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3"
- dependencies:
- asn1 "~0.2.3"
- assert-plus "^1.0.0"
- dashdash "^1.12.0"
- getpass "^0.1.1"
- optionalDependencies:
- bcrypt-pbkdf "^1.0.0"
- ecc-jsbn "~0.1.1"
- jsbn "~0.1.0"
- tweetnacl "~0.14.0"
-
-stream-browserify@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
- dependencies:
- inherits "~2.0.1"
- readable-stream "^2.0.2"
-
-stream-http@^2.3.1:
- version "2.7.2"
- resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad"
- dependencies:
- builtin-status-codes "^3.0.0"
- inherits "^2.0.1"
- readable-stream "^2.2.6"
- to-arraybuffer "^1.0.0"
- xtend "^4.0.0"
-
-string-width@^1.0.1, string-width@^1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
- dependencies:
- code-point-at "^1.0.0"
- is-fullwidth-code-point "^1.0.0"
- strip-ansi "^3.0.0"
-
string-width@^2.0.0, string-width@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -2806,21 +1793,13 @@ string-width@^2.0.0, string-width@^2.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^4.0.0"
-string_decoder@^0.10.25:
- version "0.10.31"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
-
string_decoder@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
dependencies:
safe-buffer "~5.1.0"
-stringstream@~0.0.4:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
-
-strip-ansi@^3.0.0, strip-ansi@^3.0.1:
+strip-ansi@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
dependencies:
@@ -2836,10 +1815,6 @@ strip-bom@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-strip-eof@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
-
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
@@ -2848,7 +1823,7 @@ supports-color@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
-supports-color@^4.0.0, supports-color@^4.2.1:
+supports-color@^4.0.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836"
dependencies:
@@ -2865,31 +1840,6 @@ table@^4.0.1:
slice-ansi "0.0.4"
string-width "^2.0.0"
-tapable@^0.2.7:
- version "0.2.7"
- resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.7.tgz#e46c0daacbb2b8a98b9b0cea0f4052105817ed5c"
-
-tar-pack@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984"
- dependencies:
- debug "^2.2.0"
- fstream "^1.0.10"
- fstream-ignore "^1.0.5"
- once "^1.3.3"
- readable-stream "^2.1.4"
- rimraf "^2.5.1"
- tar "^2.2.1"
- uid-number "^0.0.6"
-
-tar@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1"
- dependencies:
- block-stream "*"
- fstream "^1.0.2"
- inherits "2"
-
text-table@~0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
@@ -2898,32 +1848,16 @@ through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
-timers-browserify@^2.0.2:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86"
- dependencies:
- setimmediate "^1.0.4"
-
tmp@^0.0.31:
version "0.0.31"
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7"
dependencies:
os-tmpdir "~1.0.1"
-to-arraybuffer@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43"
-
to-fast-properties@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47"
-tough-cookie@~2.3.0:
- version "2.3.2"
- resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a"
- dependencies:
- punycode "^1.4.1"
-
trim-right@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
@@ -2932,20 +1866,6 @@ tryit@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb"
-tty-browserify@0.0.0:
- version "0.0.0"
- resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
-
-tunnel-agent@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
- dependencies:
- safe-buffer "^5.0.1"
-
-tweetnacl@^0.14.3, tweetnacl@~0.14.0:
- version "0.14.5"
- resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
-
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
@@ -2956,52 +1876,17 @@ typedarray@^0.0.6:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
-uglify-js@^2.8.29:
- version "2.8.29"
- resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd"
- dependencies:
- source-map "~0.5.1"
- yargs "~3.10.0"
- optionalDependencies:
- uglify-to-browserify "~1.0.0"
-
-uglify-to-browserify@~1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
-
-uglifyjs-webpack-plugin@^0.4.6:
- version "0.4.6"
- resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309"
+uglify-es@^3.3.7:
+ version "3.3.9"
+ resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677"
dependencies:
- source-map "^0.5.6"
- uglify-js "^2.8.29"
- webpack-sources "^1.0.1"
-
-uid-number@^0.0.6:
- version "0.0.6"
- resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81"
-
-url@^0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
- dependencies:
- punycode "1.3.2"
- querystring "0.2.0"
+ commander "~2.13.0"
+ source-map "~0.6.1"
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
-util@0.10.3, util@^0.10.3:
- version "0.10.3"
- resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9"
- dependencies:
- inherits "2.0.1"
-
-uuid@^3.0.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04"
-
validate-npm-package-license@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc"
@@ -3009,63 +1894,9 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
-verror@1.3.6:
- version "1.3.6"
- resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c"
- dependencies:
- extsprintf "1.0.2"
-
-vm-browserify@0.0.4:
- version "0.0.4"
- resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73"
- dependencies:
- indexof "0.0.1"
-
-watchpack@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac"
- dependencies:
- async "^2.1.2"
- chokidar "^1.7.0"
- graceful-fs "^4.1.2"
-
-webpack-sources@^1.0.1:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf"
- dependencies:
- source-list-map "^2.0.0"
- source-map "~0.5.3"
-
-webpack@^3.4.0:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.0.tgz#e9465b660ad79dd2d33874d968b31746ea9a8e63"
- dependencies:
- acorn "^5.0.0"
- acorn-dynamic-import "^2.0.0"
- ajv "^5.1.5"
- ajv-keywords "^2.0.0"
- async "^2.1.2"
- enhanced-resolve "^3.4.0"
- escope "^3.6.0"
- interpret "^1.0.0"
- json-loader "^0.5.4"
- json5 "^0.5.1"
- loader-runner "^2.3.0"
- loader-utils "^1.1.0"
- memory-fs "~0.4.1"
- mkdirp "~0.5.0"
- node-libs-browser "^2.0.0"
- source-map "^0.5.3"
- supports-color "^4.2.1"
- tapable "^0.2.7"
- uglifyjs-webpack-plugin "^0.4.6"
- watchpack "^1.4.0"
- webpack-sources "^1.0.1"
- yargs "^8.0.2"
-
-which-module@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+vlq@^0.2.2:
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
which@^1.2.9:
version "1.2.14"
@@ -3073,31 +1904,10 @@ which@^1.2.9:
dependencies:
isexe "^2.0.0"
-wide-align@^1.1.0:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710"
- dependencies:
- string-width "^1.0.2"
-
-window-size@0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
-
-wordwrap@0.0.2:
- version "0.0.2"
- resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
-
wordwrap@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
-wrap-ansi@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
- dependencies:
- string-width "^1.0.1"
- strip-ansi "^3.0.1"
-
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -3108,47 +1918,6 @@ write@^0.2.1:
dependencies:
mkdirp "^0.5.1"
-xtend@^4.0.0:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
-
-y18n@^3.2.1:
- version "3.2.1"
- resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
-
yallist@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
-
-yargs-parser@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9"
- dependencies:
- camelcase "^4.1.0"
-
-yargs@^8.0.2:
- version "8.0.2"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360"
- dependencies:
- camelcase "^4.1.0"
- cliui "^3.2.0"
- decamelize "^1.1.1"
- get-caller-file "^1.0.1"
- os-locale "^2.0.0"
- read-pkg-up "^2.0.0"
- require-directory "^2.1.1"
- require-main-filename "^1.0.1"
- set-blocking "^2.0.0"
- string-width "^2.0.0"
- which-module "^2.0.0"
- y18n "^3.2.1"
- yargs-parser "^7.0.0"
-
-yargs@~3.10.0:
- version "3.10.0"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
- dependencies:
- camelcase "^1.0.2"
- cliui "^2.1.0"
- decamelize "^1.0.0"
- window-size "0.1.0"