shopify_app 13.4.0 → 14.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f4300bbcbbf4e0e2c6b64d3943a83f8eda2774615016551b109880b21cb32d6
4
- data.tar.gz: 68eca06abf0153e592b7a167bcaf0b60720213e351355737c59eafba01f65e82
3
+ metadata.gz: eb2a8dba3b13ca38af9a6645ce033602f9605a1302fb83a92029715d4331ae5f
4
+ data.tar.gz: e7b3a88c503f8422dca96c05c8ef20d90c8f987bc6a6ce3741ac371ac51b454f
5
5
  SHA512:
6
- metadata.gz: 9052491aa073afeacbb8cc6dc5bd6e12fe73c4935ed36e823ed5b3966df8c240d1e1a48e9adf02444c79e4aacada04b85e0de8184ea8148b9227589ef600fc6c
7
- data.tar.gz: a78d6b3a5fb2a02c3122afabd2a4b2e564f5e5d0c7cb530622004e39143f695e0fbb804a9be20379a245e600330e08e3e8837dfb9dafe0ad8ceeade613c07c3d
6
+ metadata.gz: 9a32e20a2cf0042fd6d68ba7c43dec78814852d6742f9190d30ac929f068ce4929a8c8cfec722be4aa399b90875ace6bd52a26cb533c728c37e59258ea8db92d
7
+ data.tar.gz: e16e9417de76b0d7cb78bd2f9501cd78fab1784a570640dd153d90641b92420e51f2eb137c3f47fb07ab6f9d72f66008a8201330db6eb5e75b785e669837667a
@@ -0,0 +1,6 @@
1
+ Before submitting the PR, please consider if any of the following are needed:
2
+
3
+ - [ ] Update `CHANGELOG.md` if the changes would impact users
4
+ - [ ] Update `README.md`, if appropriate.
5
+ - [ ] Update any relevant pages in `docs/`, if necessary
6
+ - [ ] For security fixes, the [Disclosure Policy](https://github.com/Shopify/shopify_app/blob/master/SECURITY.md#disclosure-policy) must be followed.
@@ -13,7 +13,6 @@ cache:
13
13
  yarn: true
14
14
 
15
15
  rvm:
16
- - 2.4
17
16
  - 2.5
18
17
  - 2.6
19
18
  - 2.7
@@ -1,3 +1,25 @@
1
+ 14.2.0
2
+ ------
3
+ * Revert "Replace redirect calls to use App Bridge redirect functionality"
4
+
5
+ 14.1.0
6
+ ------
7
+ * Replace redirect calls to use App Bridge redirect functionality
8
+
9
+ 14.0.0
10
+ ------
11
+ * Ruby 2.4 is no longer supported by this gem
12
+ * Bump gemspec ruby dependency to 2.5
13
+ * (Beta) Add `--with-session-token` flag to the Shopify App generator to create an app that is compatible with App Bridge Authentication
14
+
15
+ 13.5.0
16
+ ------
17
+ * Add `signal_access_token_required` helper method for apps to indicate access token has expired and that a new one is required
18
+
19
+ 13.4.1
20
+ ------
21
+ * Fix the version checks for the dependency on `shopify_api` to allow all of v9.X
22
+
1
23
  13.4.0
2
24
  ------
3
25
  * Skip CSRF protection if a valid signed JWT token is present as we trust Shopify to be the only source that can sign it securely. [#994](https://github.com/Shopify/shopify_app/pull/994)
data/README.md CHANGED
@@ -74,7 +74,7 @@ Generators
74
74
 
75
75
  ### Default Generator
76
76
 
77
- The default generator will run the `install`, `shop`, and `home_controller` generators. This is the recommended way to start a new app from scratch:
77
+ The default generator will run the `install`, `shop`, `authenticated_controller`, and `home_controller` generators. This is the recommended way to start a new app from scratch:
78
78
 
79
79
  ```sh
80
80
  $ rails generate shopify_app
@@ -123,6 +123,16 @@ $ rails generate shopify_app:home_controller
123
123
 
124
124
  This generator creates an example home controller and view which fetches and displays products using the Shopify API.
125
125
 
126
+ Options include:
127
+ * __[beta]__ `with-session-token`: This flag generates an unauthenticated home_controller and a protected sample products_controller. It also creates a home view that leverages a session token to fetch products from your products_controller. Use this flag if you plan to build a single-page application or to secure your app using JWT session tokens (e.g. `--with-session-token` or `--with-session-token true`).
128
+
129
+ ### Products Controller Generator
130
+
131
+ ```sh
132
+ $ rails generate shopify_app:products_controller
133
+ ```
134
+
135
+ This generator creates an example products API controller to fetch products using the Shopify API.
126
136
 
127
137
  ### App Proxy Controller Generator
128
138
 
@@ -65,9 +65,9 @@ module ShopifyApp
65
65
  end
66
66
 
67
67
  def associated_user
68
- return unless auth_hash['extra'].present?
68
+ return unless auth_hash.dig('extra', 'associated_user').present?
69
69
 
70
- auth_hash['extra']['associated_user']
70
+ auth_hash['extra']['associated_user'].merge('scope' => auth_hash['extra']['associated_user_scope'])
71
71
  end
72
72
 
73
73
  def associated_user_id
@@ -1,18 +1,18 @@
1
1
  Releasing ShopifyApp
2
2
 
3
3
  1. Check the Semantic Versioning page for info on how to version the new release: http://semver.org
4
- 2. Create a pull request with the following changes:
5
- * Update the version of ShopifyApp in lib/shopify_app/version.rb
6
- * Update the version of shopify_app in package.json
7
- * Add a CHANGELOG entry for the new release with the date
8
- * Change the title of the PR to something like: "Packaging for release X.Y.Z"
9
- 3. Merge your pull request
10
- 4. Pull from master so you have the latest version of the shopify_app
11
- 5. Tag the HEAD with the version (Leave REV blank for HEAD or provide a SHA)
12
- $ git tag vX.Y.Z
13
- 6. Push out your tags
14
- $ git push --tags
15
- 7. Use Shipit to build and push the gem
4
+ 1. Create a pull request with the following changes:
5
+ - Update the version of ShopifyApp in lib/shopify_app/version.rb
6
+ - Update the version of shopify_app in package.json
7
+ - Add a CHANGELOG entry for the new release with the date
8
+ - Change the title of the PR to something like: "Packaging for release X.Y.Z"
9
+ 1. Merge your pull request
10
+ 1. Checkout and pull from master so you have the latest version of the shopify_app
11
+ 1. Tag the HEAD with the version
12
+ ```bash
13
+ $ git tag -f vX.Y.Z && git push --tags --force
14
+ ```
15
+ 1. Use Shipit to build and push the gem
16
16
 
17
17
  If you see an error like 'You need to create the vX.Y.X tag first', clear GIT
18
18
  cache in Shipit settings
@@ -7,7 +7,7 @@ module ShopifyApp
7
7
  class AuthenticatedControllerGenerator < Rails::Generators::Base
8
8
  source_root File.expand_path('../templates', __FILE__)
9
9
 
10
- def create_home_controller
10
+ def create_authenticated_controller
11
11
  template('authenticated_controller.rb', 'app/controllers/authenticated_controller.rb')
12
12
  end
13
13
  end
@@ -6,21 +6,39 @@ module ShopifyApp
6
6
  class HomeControllerGenerator < Rails::Generators::Base
7
7
  source_root File.expand_path('../templates', __FILE__)
8
8
 
9
+ class_option :with_session_token, type: :boolean, default: false
10
+
9
11
  def create_home_controller
10
- template('home_controller.rb', 'app/controllers/home_controller.rb')
12
+ @with_session_token = options['with_session_token']
13
+
14
+ template(home_controller_template, 'app/controllers/home_controller.rb')
15
+ end
16
+
17
+ def create_products_controller
18
+ generate("shopify_app:products_controller") if with_session_token?
11
19
  end
12
20
 
13
21
  def create_home_index_view
14
- copy_file('index.html.erb', 'app/views/home/index.html.erb')
22
+ template('index.html.erb', 'app/views/home/index.html.erb')
15
23
  end
16
24
 
17
25
  def add_home_index_route
18
26
  route("root :to => 'home#index'")
19
27
  end
20
28
 
29
+ private
30
+
21
31
  def embedded_app?
22
32
  ShopifyApp.configuration.embedded_app?
23
33
  end
34
+
35
+ def with_session_token?
36
+ @with_session_token
37
+ end
38
+
39
+ def home_controller_template
40
+ with_session_token? ? 'unauthenticated_home_controller.rb' : 'home_controller.rb'
41
+ end
24
42
  end
25
43
  end
26
44
  end
@@ -1,21 +1,71 @@
1
- <h2>Products</h2>
1
+ <!DOCTYPE html>
2
+ <html lang="<%= I18n.locale %>">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <link
7
+ rel="stylesheet"
8
+ href="https://unpkg.com/@shopify/polaris@4.25.0/styles.min.css"
9
+ />
10
+ <% if @with_session_token %> <script>
11
+ document.addEventListener("DOMContentLoaded", async function() {
12
+ var SessionToken = window["app-bridge"].actions.SessionToken
13
+ var app = window.app;
2
14
 
3
- <ul>
4
- <% @products.each do |product| %>
5
- <li><%= link_to product.title, "https://#{@current_shopify_session.domain}/admin/products/#{product.id}", target: "_top" %></li>
6
- <% end %>
7
- </ul>
15
+ app.dispatch(
16
+ SessionToken.request(),
17
+ );
8
18
 
9
- <hr>
19
+ // Save a session token for future requests
20
+ window.sessionToken = await new Promise((resolve) => {
21
+ app.subscribe(SessionToken.ActionType.RESPOND, (data) => {
22
+ resolve(data.sessionToken || "");
23
+ });
24
+ });
10
25
 
11
- <h2>Webhooks</h2>
26
+ var fetchProducts = function() {
27
+ var headers = new Headers({ "Authorization": "Bearer " + window.sessionToken });
28
+ return fetch("/products", { headers })
29
+ .then(response => response.json())
30
+ .then(data => {
31
+ var products = data.products;
12
32
 
13
- <% if @webhooks.present? %>
14
- <ul>
15
- <% @webhooks.each do |webhook| %>
16
- <li><%= webhook.topic %> : <%= webhook.address %></li>
17
- <% end %>
18
- </ul>
19
- <% else %>
20
- <p>This app has not created any webhooks for this Shop. Add webhooks to your ShopifyApp initializer if you need webhooks</p>
21
- <% end %>
33
+ if (products === undefined || products.length == 0) {
34
+ document.getElementById("products").innerHTML = "<br>No products to display.";
35
+ } else {
36
+ var list = "";
37
+ products.forEach((product) => {
38
+ var link = `<a target="_top" href="https://<%%= @shop_origin %>/admin/products/${product.id}">`
39
+ list += "<li>" + link + product.title + "</a></li>";
40
+ });
41
+ document.getElementById("products").innerHTML = "<ul>" + list + "</ul>";
42
+ }
43
+ });
44
+ }();
45
+ });
46
+ </script>
47
+ <% end %> </head>
48
+ <body>
49
+ <h2>Products</h2>
50
+ <% if @with_session_token %> <div id="products"><br>Loading...</div><% else %>
51
+ <ul>
52
+ <%% @products.each do |product| %>
53
+ <li><%%= link_to product.title, "https://#{@current_shopify_session.domain}/admin/products/#{product.id}", target: "_top" %></li>
54
+ <%% end %>
55
+ </ul>
56
+
57
+ <hr>
58
+
59
+ <h2>Webhooks</h2>
60
+
61
+ <%% if @webhooks.present? %>
62
+ <ul>
63
+ <%% @webhooks.each do |webhook| %>
64
+ <li><%%= webhook.topic %> : <%%= webhook.address %></li>
65
+ <%% end %>
66
+ </ul>
67
+ <%% else %>
68
+ <p>This app has not created any webhooks for this Shop. Add webhooks to your ShopifyApp initializer if you need webhooks</p>
69
+ <%% end %><% end %>
70
+ </body>
71
+ </html>
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HomeController < ApplicationController
4
+ include ShopifyApp::EmbeddedApp
5
+ include ShopifyApp::RequireKnownShop
6
+
7
+ def index
8
+ @shop_origin = current_shopify_domain
9
+ end
10
+ end
@@ -28,7 +28,7 @@
28
28
 
29
29
  <%= content_tag(:div, nil, id: 'shopify-app-init', data: {
30
30
  api_key: ShopifyApp.configuration.api_key,
31
- shop_origin: (@current_shopify_session.domain if @current_shopify_session),
31
+ shop_origin: @shop_origin || (@current_shopify_session.domain if @current_shopify_session),
32
32
  debug: Rails.env.development?
33
33
  } ) %>
34
34
 
@@ -9,6 +9,7 @@ ShopifyApp.configure do |config|
9
9
  config.after_authenticate_job = false
10
10
  config.api_version = "<%= @api_version %>"
11
11
  config.shop_session_repository = 'Shop'
12
+ config.allow_jwt_authentication = true
12
13
  end
13
14
 
14
15
  # ShopifyApp::Utils.fetch_known_api_versions # Uncomment to fetch known api versions from shopify servers on boot
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators/base'
4
+
5
+ module ShopifyApp
6
+ module Generators
7
+ class ProductsControllerGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('../templates', __FILE__)
9
+
10
+ def create_products_controller
11
+ template('products_controller.rb', 'app/controllers/products_controller.rb')
12
+ end
13
+
14
+ def add_products_route
15
+ route("get '/products', :to => 'products#index'")
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ProductsController < AuthenticatedController
4
+ def index
5
+ @products = ShopifyAPI::Product.find(:all, params: { limit: 10 })
6
+ render(json: { products: @products })
7
+ end
8
+ end
@@ -11,7 +11,7 @@ module ShopifyApp
11
11
  generate("shopify_app:install #{@opts.join(' ')}")
12
12
  generate("shopify_app:shop_model")
13
13
  generate("shopify_app:authenticated_controller")
14
- generate("shopify_app:home_controller")
14
+ generate("shopify_app:home_controller #{@opts.join(' ')}")
15
15
  end
16
16
  end
17
17
  end
@@ -14,6 +14,8 @@ module ShopifyApp
14
14
  rescue_from ActiveResource::UnauthorizedAccess, with: :close_session
15
15
  end
16
16
 
17
+ ACCESS_TOKEN_REQUIRED_HEADER = 'X-Shopify-API-Request-Failure-Unauthorized'
18
+
17
19
  def activate_shopify_session
18
20
  return redirect_to_login if current_shopify_session.blank?
19
21
  clear_top_level_oauth_cookie
@@ -80,6 +82,10 @@ module ShopifyApp
80
82
  end
81
83
  end
82
84
 
85
+ def signal_access_token_required
86
+ response.set_header(ACCESS_TOKEN_REQUIRED_HEADER, true)
87
+ end
88
+
83
89
  protected
84
90
 
85
91
  def jwt_shopify_domain
@@ -204,6 +210,8 @@ module ShopifyApp
204
210
  def return_address
205
211
  return base_return_address unless ShopifyApp.configuration.allow_jwt_authentication
206
212
  return_address_with_params(shop: current_shopify_domain)
213
+ rescue ShopifyDomainNotFound
214
+ base_return_address
207
215
  end
208
216
 
209
217
  def base_return_address
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ShopifyApp
3
- VERSION = '13.4.0'
3
+ VERSION = '14.2.0'
4
4
  end
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shopify_app",
3
- "version": "13.0.0",
3
+ "version": "13.6.0",
4
4
  "lockfileVersion": 1,
5
5
  "requires": true,
6
6
  "dependencies": {
@@ -53,12 +53,6 @@
53
53
  "minimist": "^1.2.0"
54
54
  }
55
55
  },
56
- "lodash": {
57
- "version": "4.17.15",
58
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
59
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
60
- "dev": true
61
- },
62
56
  "ms": {
63
57
  "version": "2.1.2",
64
58
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -78,14 +72,6 @@
78
72
  "lodash": "^4.17.13",
79
73
  "source-map": "^0.5.0",
80
74
  "trim-right": "^1.0.1"
81
- },
82
- "dependencies": {
83
- "lodash": {
84
- "version": "4.17.15",
85
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
86
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
87
- "dev": true
88
- }
89
75
  }
90
76
  },
91
77
  "@babel/helper-annotate-as-pure": {
@@ -151,14 +137,6 @@
151
137
  "@babel/helper-function-name": "^7.1.0",
152
138
  "@babel/types": "^7.5.5",
153
139
  "lodash": "^4.17.13"
154
- },
155
- "dependencies": {
156
- "lodash": {
157
- "version": "4.17.15",
158
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
159
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
160
- "dev": true
161
- }
162
140
  }
163
141
  },
164
142
  "@babel/helper-explode-assignable-expression": {
@@ -230,14 +208,6 @@
230
208
  "@babel/template": "^7.4.4",
231
209
  "@babel/types": "^7.5.5",
232
210
  "lodash": "^4.17.13"
233
- },
234
- "dependencies": {
235
- "lodash": {
236
- "version": "4.17.15",
237
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
238
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
239
- "dev": true
240
- }
241
211
  }
242
212
  },
243
213
  "@babel/helper-optimise-call-expression": {
@@ -262,14 +232,6 @@
262
232
  "dev": true,
263
233
  "requires": {
264
234
  "lodash": "^4.17.13"
265
- },
266
- "dependencies": {
267
- "lodash": {
268
- "version": "4.17.15",
269
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
270
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
271
- "dev": true
272
- }
273
235
  }
274
236
  },
275
237
  "@babel/helper-remap-async-to-generator": {
@@ -598,14 +560,6 @@
598
560
  "requires": {
599
561
  "@babel/helper-plugin-utils": "^7.0.0",
600
562
  "lodash": "^4.17.13"
601
- },
602
- "dependencies": {
603
- "lodash": {
604
- "version": "4.17.15",
605
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
606
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
607
- "dev": true
608
- }
609
563
  }
610
564
  },
611
565
  "@babel/plugin-transform-classes": {
@@ -1056,12 +1010,6 @@
1056
1010
  "ms": "^2.1.1"
1057
1011
  }
1058
1012
  },
1059
- "lodash": {
1060
- "version": "4.17.15",
1061
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
1062
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
1063
- "dev": true
1064
- },
1065
1013
  "ms": {
1066
1014
  "version": "2.1.2",
1067
1015
  "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -1079,14 +1027,6 @@
1079
1027
  "esutils": "^2.0.2",
1080
1028
  "lodash": "^4.17.13",
1081
1029
  "to-fast-properties": "^2.0.0"
1082
- },
1083
- "dependencies": {
1084
- "lodash": {
1085
- "version": "4.17.15",
1086
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
1087
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
1088
- "dev": true
1089
- }
1090
1030
  }
1091
1031
  },
1092
1032
  "@sinonjs/commons": {
@@ -1117,14 +1057,6 @@
1117
1057
  "@sinonjs/commons": "^1.3.0",
1118
1058
  "array-from": "^2.1.1",
1119
1059
  "lodash": "^4.17.15"
1120
- },
1121
- "dependencies": {
1122
- "lodash": {
1123
- "version": "4.17.15",
1124
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
1125
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
1126
- "dev": true
1127
- }
1128
1060
  }
1129
1061
  },
1130
1062
  "@sinonjs/text-encoding": {
@@ -2600,9 +2532,9 @@
2600
2532
  "dev": true
2601
2533
  },
2602
2534
  "elliptic": {
2603
- "version": "6.5.1",
2604
- "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz",
2605
- "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==",
2535
+ "version": "6.5.3",
2536
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
2537
+ "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
2606
2538
  "dev": true,
2607
2539
  "requires": {
2608
2540
  "bn.js": "^4.4.0",
@@ -4579,9 +4511,9 @@
4579
4511
  }
4580
4512
  },
4581
4513
  "lodash": {
4582
- "version": "4.17.15",
4583
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
4584
- "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
4514
+ "version": "4.17.19",
4515
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
4516
+ "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
4585
4517
  "dev": true
4586
4518
  },
4587
4519
  "log-symbols": {