diff --git a/README.md b/README.md
index a0ee0b7..5a1aa30 100644
--- a/README.md
+++ b/README.md
@@ -4,5 +4,5 @@
```bash
$ docker-compose up -d
-$ docker-compose exec app bundle exec rails db:create
+$ docker-compose exec app bundle exec rails db:setup
```
diff --git a/app/assets/stylesheets/records.scss b/app/assets/stylesheets/records.scss
new file mode 100644
index 0000000..9ec3201
--- /dev/null
+++ b/app/assets/stylesheets/records.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Records controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/records_controller.rb b/app/controllers/records_controller.rb
new file mode 100644
index 0000000..1caf4c1
--- /dev/null
+++ b/app/controllers/records_controller.rb
@@ -0,0 +1,16 @@
+class RecordsController < ApplicationController
+ def index
+ @records = Record.all
+ @record = Record.new
+ end
+
+ def create
+ record_params = params.require(:record).permit(:voice)
+ @record = Record.new(record_params)
+ if @record.save
+ head :created
+ else
+ head :unprocessable_entity
+ end
+ end
+end
diff --git a/app/helpers/records_helper.rb b/app/helpers/records_helper.rb
new file mode 100644
index 0000000..e63c599
--- /dev/null
+++ b/app/helpers/records_helper.rb
@@ -0,0 +1,2 @@
+module RecordsHelper
+end
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 9cd55d4..feba9c7 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -3,11 +3,15 @@
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
-require("@rails/ujs").start()
-require("turbolinks").start()
-require("@rails/activestorage").start()
-require("channels")
-
+require("@rails/ujs").start();
+require("turbolinks").start();
+require("@rails/activestorage").start();
+require("channels");
+const OpusMediaRecorder = require("opus-media-recorder");
+const Worker = require("opus-media-recorder/encoderWorker.js");
+const OggOpusWasm = require("opus-media-recorder/OggOpusEncoder.wasm");
+const WebMOpusWasm = require("opus-media-recorder/WebMOpusEncoder.wasm");
+const axios = require("axios");
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
@@ -15,3 +19,89 @@ require("channels")
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
+
+// Polyfill
+window.MediaRecorder = OpusMediaRecorder;
+
+document.addEventListener("turbolinks:load", init);
+
+function init() {
+ if (!navigator.mediaDevices.getUserMedia) return;
+
+ const soundClips = document.querySelector("#js-sound-clips");
+ const startButton = document.querySelector("#js-start-recording");
+ const stopButton = document.querySelector("#js-stop-recording");
+
+ startButton.disabled = false;
+ stopButton.disabled = true;
+
+ const constraints = { audio: true };
+ let chunks = [];
+
+ // iOS Safari は ogg の再生ができない
+ // このサンプルでは容量が大きくなってもやむなしとして wav で
+ const mimeType = "audio/wav";
+
+ const onSuccess = stream => {
+ const options = {
+ mimeType: mimeType
+ };
+ const workerOptions = {
+ encoderWorkerFactory: _ => new Worker(),
+ OggOpusEncoderWasmPath: OggOpusWasm,
+ WebMOpusEncoderWasmPath: WebMOpusWasm
+ };
+ const mediaRecorder = new MediaRecorder(stream, options, workerOptions);
+
+ startButton.addEventListener("click", () => {
+ mediaRecorder.start();
+ console.log(mediaRecorder.state);
+ console.log("recorder started");
+ startButton.disabled = true;
+ stopButton.disabled = false;
+ });
+
+ stopButton.addEventListener("click", () => {
+ mediaRecorder.stop();
+ console.log(mediaRecorder.state);
+ console.log("recorder stopped");
+ startButton.disabled = false;
+ stopButton.disabled = true;
+ });
+
+ mediaRecorder.onstop = e => {
+ console.log("data available after MediaRecorder.stop() called.");
+
+ var blob = new Blob(chunks, { type: mimeType });
+ chunks = [];
+
+ var uploadFormData = new FormData();
+ var csrfParam = document.querySelector("meta[name='csrf-param']").content;
+ var csrfToken = document.querySelector("meta[name='csrf-token']").content;
+ uploadFormData.append(csrfParam, csrfToken);
+ uploadFormData.append("record[voice]", blob);
+
+ axios
+ .post("/records", uploadFormData)
+ .then(result => {
+ console.log(result);
+ window.location.reload(true);
+ })
+ .catch(error => {
+ console.log(error);
+ });
+
+ console.log("recorder stopped");
+ };
+
+ mediaRecorder.ondataavailable = e => {
+ chunks.push(e.data);
+ };
+ };
+
+ var onError = err => {
+ console.log("The following error occured: " + err);
+ };
+
+ navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
+}
diff --git a/app/models/record.rb b/app/models/record.rb
new file mode 100644
index 0000000..13dfd2a
--- /dev/null
+++ b/app/models/record.rb
@@ -0,0 +1,3 @@
+class Record < ApplicationRecord
+ has_one_attached :voice
+end
diff --git a/app/views/records/index.html.erb b/app/views/records/index.html.erb
new file mode 100644
index 0000000..735d80d
--- /dev/null
+++ b/app/views/records/index.html.erb
@@ -0,0 +1,14 @@
+
Records#index
+Find me in app/views/records/index.html.erb
+
+
+
+
+
+
+<% @records.each do |record| %>
+
+ <%= record.created_at %>
+ <%= audio_tag rails_blob_url(record.voice, disposition: "attachment"), controls: true %>
+
+<% end %>
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
index dc18996..b89d91b 100644
--- a/config/initializers/mime_types.rb
+++ b/config/initializers/mime_types.rb
@@ -2,3 +2,6 @@
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
+
+# public assets
+Rack::Mime::MIME_TYPES[".wasm"] = "application/wasm"
diff --git a/config/routes.rb b/config/routes.rb
index c06383a..28a0ca0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,3 +1,4 @@
Rails.application.routes.draw do
- # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
+ root "records#index"
+ resources :records
end
diff --git a/config/webpack/environment.js b/config/webpack/environment.js
index d16d9af..ac9101b 100644
--- a/config/webpack/environment.js
+++ b/config/webpack/environment.js
@@ -1,3 +1,22 @@
-const { environment } = require('@rails/webpacker')
+const { environment } = require("@rails/webpacker");
-module.exports = environment
+const opusMediaRecorderEncoderWorkerLoader = {
+ test: /opus-media-recorder\/encoderWorker\.js$/,
+ loader: "worker-loader"
+};
+const opusMediaRecorderWasmLoader = {
+ test: /opus-media-recorder\/.*\.wasm$/,
+ type: "javascript/auto",
+ loader: "file-loader"
+};
+
+environment.loaders.prepend(
+ "opusMediaRecorderEncoderWorkerLoader",
+ opusMediaRecorderEncoderWorkerLoader
+);
+environment.loaders.prepend(
+ "opusMediaRecorderWasmLoader",
+ opusMediaRecorderWasmLoader
+);
+
+module.exports = environment;
diff --git a/db/migrate/20190703125820_create_active_storage_tables.active_storage.rb b/db/migrate/20190703125820_create_active_storage_tables.active_storage.rb
new file mode 100644
index 0000000..0b2ce25
--- /dev/null
+++ b/db/migrate/20190703125820_create_active_storage_tables.active_storage.rb
@@ -0,0 +1,27 @@
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
+ def change
+ create_table :active_storage_blobs do |t|
+ t.string :key, null: false
+ t.string :filename, null: false
+ t.string :content_type
+ t.text :metadata
+ t.bigint :byte_size, null: false
+ t.string :checksum, null: false
+ t.datetime :created_at, null: false
+
+ t.index [ :key ], unique: true
+ end
+
+ create_table :active_storage_attachments do |t|
+ t.string :name, null: false
+ t.references :record, null: false, polymorphic: true, index: false
+ t.references :blob, null: false
+
+ 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/db/migrate/20190703130233_create_records.rb b/db/migrate/20190703130233_create_records.rb
new file mode 100644
index 0000000..f4c61f7
--- /dev/null
+++ b/db/migrate/20190703130233_create_records.rb
@@ -0,0 +1,8 @@
+class CreateRecords < ActiveRecord::Migration[6.0]
+ def change
+ create_table :records do |t|
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000..45e2705
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,45 @@
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# This file is the source Rails uses to define your schema when running `rails
+# db:schema:load`. When creating a new database, `rails db:schema:load` tends to
+# be faster and is potentially less error prone than running all of your
+# migrations from scratch. Old migrations may fail to apply correctly if those
+# migrations use external dependencies or application code.
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 2019_07_03_130233) do
+
+ # These are extensions that must be enabled in order to support this database
+ enable_extension "plpgsql"
+
+ create_table "active_storage_attachments", force: :cascade do |t|
+ t.string "name", null: false
+ t.string "record_type", null: false
+ t.bigint "record_id", null: false
+ t.bigint "blob_id", null: false
+ t.datetime "created_at", null: false
+ t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
+ t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
+ end
+
+ create_table "active_storage_blobs", force: :cascade do |t|
+ t.string "key", null: false
+ t.string "filename", null: false
+ t.string "content_type"
+ t.text "metadata"
+ t.bigint "byte_size", null: false
+ t.string "checksum", null: false
+ t.datetime "created_at", null: false
+ t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
+ end
+
+ create_table "records", force: :cascade do |t|
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ end
+
+ add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
+end
diff --git a/package.json b/package.json
index 459889f..7b17893 100644
--- a/package.json
+++ b/package.json
@@ -6,10 +6,13 @@
"@rails/activestorage": "^6.0.0-alpha",
"@rails/ujs": "^6.0.0-alpha",
"@rails/webpacker": "^4.0.7",
+ "axios": "^0.19.0",
"turbolinks": "^5.2.0"
},
"version": "0.1.0",
"devDependencies": {
- "webpack-dev-server": "^3.7.2"
+ "opus-media-recorder": "^0.7.19",
+ "webpack-dev-server": "^3.7.2",
+ "worker-loader": "^2.0.0"
}
}
diff --git a/spec/controllers/records_controller_spec.rb b/spec/controllers/records_controller_spec.rb
new file mode 100644
index 0000000..c629c9b
--- /dev/null
+++ b/spec/controllers/records_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+RSpec.describe RecordsController, type: :controller do
+
+ describe "GET #index" do
+ it "returns http success" do
+ get :index
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+end
diff --git a/spec/helpers/records_helper_spec.rb b/spec/helpers/records_helper_spec.rb
new file mode 100644
index 0000000..6ab29f1
--- /dev/null
+++ b/spec/helpers/records_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the RecordsHelper. For example:
+#
+# describe RecordsHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe RecordsHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/record_spec.rb b/spec/models/record_spec.rb
new file mode 100644
index 0000000..50334b9
--- /dev/null
+++ b/spec/models/record_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Record, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/views/records/index.html.erb_spec.rb b/spec/views/records/index.html.erb_spec.rb
new file mode 100644
index 0000000..74b83a2
--- /dev/null
+++ b/spec/views/records/index.html.erb_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe "records/index.html.erb", type: :view do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/yarn.lock b/yarn.lock
index 2b989d7..1203671 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1160,6 +1160,14 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
+axios@^0.19.0:
+ version "0.19.0"
+ resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
+ integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==
+ dependencies:
+ follow-redirects "1.5.10"
+ is-buffer "^2.0.2"
+
babel-loader@^8.0.6:
version "8.0.6"
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
@@ -2137,6 +2145,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
dependencies:
ms "2.0.0"
+debug@=3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
debug@^3.2.5, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
@@ -2249,6 +2264,11 @@ destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+detect-browser@^4.1.0:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-4.5.1.tgz#b9df3f66454a4f32adbc4db2949aa788b757921b"
+ integrity sha512-cGXvbxvDws+ZjzR3AI+2IcKQR3Tj85PaUn42u6A/DWOEYda5fgvkS/NrQp2lD4LZ/IE2nLE/0kV//qekOyxJ2Q==
+
detect-file@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
@@ -2483,6 +2503,11 @@ etag@~1.8.1:
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+event-target-shim@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-3.0.2.tgz#af25bb55a97c670bbeba985c82647fb64d892153"
+ integrity sha512-HK5GhnEAkm7fLy249GtF7DIuYmjLm85Ft6ssj7DhVl8Tx/z9+v0W6aiIVUdT4AXWGYy5Fc+s6gqBI49Bf0LejQ==
+
eventemitter3@^3.0.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
@@ -2733,6 +2758,13 @@ flush-write-stream@^1.0.0:
inherits "^2.0.3"
readable-stream "^2.3.6"
+follow-redirects@1.5.10:
+ version "1.5.10"
+ resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
+ integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
+ dependencies:
+ debug "=3.1.0"
+
follow-redirects@^1.0.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.7.0.tgz#489ebc198dc0e7f64167bd23b03c4c19b5784c76"
@@ -3409,6 +3441,11 @@ is-buffer@^1.1.5:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
+is-buffer@^2.0.2:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
+ integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
+
is-callable@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
@@ -3793,7 +3830,7 @@ loader-runner@^2.3.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357"
integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==
-loader-utils@1.2.3, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
+loader-utils@1.2.3, loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
@@ -4532,6 +4569,14 @@ optimize-css-assets-webpack-plugin@^5.0.1:
cssnano "^4.1.10"
last-call-webpack-plugin "^3.0.0"
+opus-media-recorder@^0.7.19:
+ version "0.7.19"
+ resolved "https://registry.yarnpkg.com/opus-media-recorder/-/opus-media-recorder-0.7.19.tgz#47696cd5723ae24b6242bba91dd98f7c2a99dc1b"
+ integrity sha512-5KmT/dSEuonD4GEZWzmfnqoAuYexcgiDlQUQhtYneTorKDhZDHQ0fS9gQe00bOWyyIumBZMMUithTQD6+dr74g==
+ dependencies:
+ detect-browser "^4.1.0"
+ event-target-shim "^3.0.2"
+
original@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f"
@@ -5939,6 +5984,14 @@ sax@^1.2.4, sax@~1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
+schema-utils@^0.4.0:
+ version "0.4.7"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187"
+ integrity sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==
+ dependencies:
+ ajv "^6.1.0"
+ ajv-keywords "^3.1.0"
+
schema-utils@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770"
@@ -7053,6 +7106,14 @@ worker-farm@^1.7.0:
dependencies:
errno "~0.1.7"
+worker-loader@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/worker-loader/-/worker-loader-2.0.0.tgz#45fda3ef76aca815771a89107399ee4119b430ac"
+ integrity sha512-tnvNp4K3KQOpfRnD20m8xltE3eWh89Ye+5oj7wXEEHKac1P4oZ6p9oTj8/8ExqoSBnk9nu5Pr4nKfQ1hn2APJw==
+ dependencies:
+ loader-utils "^1.0.0"
+ schema-utils "^0.4.0"
+
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"