avo 4.3.8 → 4.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,4 +28,48 @@ class Avo::ViewTypes::BaseViewTypeComponent < Avo::BaseComponent
28
28
  field_per_page: field&.per_page
29
29
  )
30
30
  end
31
+
32
+ def selected_page_label
33
+ if @resource.pagination_type.countless?
34
+ t "avo.x_records_selected_from_page_html", selected: @pagy.in
35
+ else
36
+ t "avo.x_records_selected_from_a_total_of_x_html", selected: @pagy.in, count: @pagy.count
37
+ end
38
+ end
39
+
40
+ def selected_all_label
41
+ if @resource.pagination_type.countless?
42
+ t "avo.records_selected_from_all_pages_html"
43
+ else
44
+ t "avo.x_records_selected_from_all_pages_html", count: @pagy.count
45
+ end
46
+ end
47
+
48
+ def encrypted_query
49
+ # A page cut by its source is all that is held, so there is nothing to select across pages
50
+ return :select_all_disabled if @query.is_a?(Avo::Resources::ArrayResource::Page)
51
+
52
+ # TODO: move this to the resource where we can apply the adapter pattern
53
+ if Module.const_defined?("Ransack::Search") && @query.instance_of?(Ransack::Search)
54
+ @query = @query.result
55
+ end
56
+
57
+ Avo::Services::EncryptionService.encrypt(message: @query, purpose: :select_all, serializer: Marshal)
58
+ rescue
59
+ disable_select_all
60
+ end
61
+
62
+ private
63
+
64
+ def disable_select_all
65
+ if Rails.env.development?
66
+ Avo.error_manager.add({
67
+ url: "https://docs.avohq.io/4.0/select-all.html#serialization-known-issues",
68
+ target: "_blank",
69
+ message: "An error occurred while serializing the query object. The Select All feature has been disabled because it depends on successful query serialization. For more details and troubleshooting steps, click here."
70
+ })
71
+ end
72
+
73
+ :select_all_disabled
74
+ end
31
75
  end
@@ -1,21 +1,75 @@
1
1
  <turbo-frame id="<%= list_frame_id %>" target="_top" class="relative flex w-full flex-col @container">
2
- <%# tabindex=-1 makes the wrapper a Shift+T focus anchor (top-level grids only, %>
3
- <%# never a has-many grid on a show page); from here Tab steps through the cards. %>
2
+ <%# The selection controller wraps the bar and the cards. The reorder gem sorts the grid wrapper's children, %>
3
+ <%# so the bar can't sit inside it. %>
4
4
  <%= content_tag :div,
5
- class: "grid-wrapper",
6
- tabindex: (@reflection.present? ? nil : "-1"),
5
+ class: "flex w-full flex-col gap-3",
7
6
  data: {
8
- component_name: self.class.to_s.underscore,
9
7
  controller: "item-select-all",
10
8
  resource_name: resource_name_for(@resource),
9
+ item_select_all_selected_all_value: false,
10
+ item_select_all_selected_all_query_value: encrypted_query,
11
+ item_select_all_page_count_value: @pagy.pages,
11
12
  selected_resources_name: resource_name_for(@resource),
12
- selected_resources: [],
13
- **(@reflection.present? ? {} : {"content-focus": ""}),
14
- **(try(:drag_reorder_data_attributes) || {})
13
+ selected_resources: []
15
14
  } do %>
16
- <% @resources.each_with_index do |resource, index| %>
17
- <% cache_if Avo.configuration.cache_resources_on_index_view, resource.index_cache_key(@parent_record, cache_context:), expires_in: 1.day do %>
18
- <%= render(resource.resolve_component(Avo::Index::GridItemComponent).new(resource: resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, actions: actions)) %>
15
+ <% if @resource.record_selector %>
16
+ <div class="flex min-h-7 flex-wrap items-center gap-x-3 gap-y-1 px-2 text-sm" data-component-name="avo/view_types/grid_select_all">
17
+ <%= content_tag :label, class: "flex items-center gap-2 cursor-pointer text-content-secondary" do %>
18
+ <%= tag :input,
19
+ type: "checkbox",
20
+ name: t("avo.select_all"),
21
+ autocomplete: :off,
22
+ data: {
23
+ action: "input->item-select-all#toggle",
24
+ item_select_all_target: "checkbox"
25
+ } %>
26
+ <%= t "avo.select_all" %>
27
+ <% end %>
28
+
29
+ <% if @pagy.pages > 1 %>
30
+ <div class="hidden border-s border-tertiary ps-3" data-item-select-all-target="selectAllOverlay">
31
+ <div data-item-select-all-target="unselectedMessage">
32
+ <%= selected_page_label %>
33
+
34
+ <%= a_link request.url,
35
+ size: :xs,
36
+ color: :primary,
37
+ class: "relative -top-[0.1rem] ms-2 cursor-pointer text-primary-500",
38
+ data: {action: "click->item-select-all#selectAll"} do %>
39
+ <%= t "avo.select_all_matching" %>
40
+ <% end %>
41
+ </div>
42
+
43
+ <div class="hidden" data-item-select-all-target="selectedMessage">
44
+ <%= selected_all_label %>
45
+
46
+ <%= a_link request.url,
47
+ size: :xs,
48
+ color: :primary,
49
+ class: "relative -top-[0.1rem] ms-2 cursor-pointer text-primary-500",
50
+ data: {action: "click->item-select-all#selectAll"} do %>
51
+ <%= t("avo.undo").humanize %>
52
+ <% end %>
53
+ </div>
54
+ </div>
55
+ <% end %>
56
+ </div>
57
+ <% end %>
58
+
59
+ <%# tabindex=-1 makes the wrapper a Shift+T focus anchor (top-level grids only, %>
60
+ <%# never a has-many grid on a show page); from here Tab steps through the cards. %>
61
+ <%= content_tag :div,
62
+ class: "grid-wrapper",
63
+ tabindex: (@reflection.present? ? nil : "-1"),
64
+ data: {
65
+ component_name: self.class.to_s.underscore,
66
+ **(@reflection.present? ? {} : {"content-focus": ""}),
67
+ **(try(:drag_reorder_data_attributes) || {})
68
+ } do %>
69
+ <% @resources.each_with_index do |resource, index| %>
70
+ <% cache_if Avo.configuration.cache_resources_on_index_view, resource.index_cache_key(@parent_record, cache_context:), expires_in: 1.day do %>
71
+ <%= render(resource.resolve_component(Avo::Index::GridItemComponent).new(resource: resource, reflection: @reflection, parent_record: @parent_record, parent_resource: @parent_resource, actions: actions)) %>
72
+ <% end %>
19
73
  <% end %>
20
74
  <% end %>
21
75
  <% end %>
@@ -24,36 +24,6 @@ class Avo::ViewTypes::TableComponent < Avo::ViewTypes::BaseViewTypeComponent
24
24
  }
25
25
  end
26
26
 
27
- def encrypted_query
28
- # A page cut by its source is all that is held, so there is nothing to select across pages
29
- return :select_all_disabled if @query.is_a?(Avo::Resources::ArrayResource::Page)
30
-
31
- # TODO: move this to the resource where we can apply the adapter pattern
32
- if Module.const_defined?("Ransack::Search") && @query.instance_of?(Ransack::Search)
33
- @query = @query.result
34
- end
35
-
36
- Avo::Services::EncryptionService.encrypt(message: @query, purpose: :select_all, serializer: Marshal)
37
- rescue
38
- disable_select_all
39
- end
40
-
41
- def selected_page_label
42
- if @resource.pagination_type.countless?
43
- t "avo.x_records_selected_from_page_html", selected: @pagy.in
44
- else
45
- t "avo.x_records_selected_from_a_total_of_x_html", selected: @pagy.in, count: @pagy.count
46
- end
47
- end
48
-
49
- def selected_all_label
50
- if @resource.pagination_type.countless?
51
- t "avo.records_selected_from_all_pages_html"
52
- else
53
- t "avo.x_records_selected_from_all_pages_html", count: @pagy.count
54
- end
55
- end
56
-
57
27
  # Table rows are not fragment-cached. A `cache_if` wrapped this until it was
58
28
  # removed: a `return` inside its block unwound past Action View's
59
29
  # `write_fragment`, so nothing was ever written, and a hit would have handed
@@ -97,18 +67,4 @@ class Avo::ViewTypes::TableComponent < Avo::ViewTypes::BaseViewTypeComponent
97
67
 
98
68
  [header_fields, table_row_components]
99
69
  end
100
-
101
- private
102
-
103
- def disable_select_all
104
- if Rails.env.development?
105
- Avo.error_manager.add({
106
- url: "https://docs.avohq.io/4.0/select-all.html#serialization-known-issues",
107
- target: "_blank",
108
- message: "An error occurred while serializing the query object. The Select All feature has been disabled because it depends on successful query serialization. For more details and troubleshooting steps, click here."
109
- })
110
- end
111
-
112
- :select_all_disabled
113
- end
114
70
  end
@@ -71,7 +71,7 @@ module Avo
71
71
  def set_query
72
72
  # If the user selected all records, use the decrypted index query
73
73
  # Otherwise, find the records from the resource ids
74
- @query = if action_params[:fields]&.dig(:avo_selected_all) == "true"
74
+ @query = if selected_all?
75
75
  decrypted_index_query
76
76
  else
77
77
  find_records_from_resource_ids
@@ -252,8 +252,23 @@ module Avo
252
252
  end
253
253
  end
254
254
 
255
+ def selected_all?
256
+ action_params[:fields]&.dig(:avo_selected_all) == "true"
257
+ end
258
+
255
259
  def verify_authorization
256
- raise Avo::NotAuthorizedError.new unless @action.authorized?
260
+ raise Avo::NotAuthorizedError.new unless @action.authorized? && act_on_authorized?
261
+ end
262
+
263
+ # The Actions button asks `act_on?`, so the run asks it too, of every record it names: the one in
264
+ # the URL and the selected ids. With none, the model class stands in, as on the index. A select-all
265
+ # run is the index query the button already allowed, and is not loaded here to ask it row by row.
266
+ def act_on_authorized?
267
+ records = [@record, *(@query unless selected_all?)].compact
268
+
269
+ (records.presence || [@resource.model_class]).all? do |record|
270
+ @resource.authorization.authorize_action(:act_on, record:, raise_exception: false)
271
+ end
257
272
  end
258
273
  end
259
274
  end
@@ -245,16 +245,21 @@ module Avo
245
245
  Avo.configuration.locale = locale if locale
246
246
  end
247
247
 
248
+ # With nowhere to go back to, the refusal renders here: redirecting to the root loops when the
249
+ # home page is the one refused.
248
250
  def render_unauthorized(exception)
249
- flash[:notice] = t "avo.not_authorized"
250
-
251
- redirect_url = if request.referrer.blank? || (request.referrer == request.url)
252
- root_url
253
- else
254
- request.referrer
251
+ if request.referrer.present? && request.referrer != request.url
252
+ flash[:notice] = t "avo.not_authorized"
253
+ return redirect_to(request.referrer)
255
254
  end
256
255
 
257
- redirect_to(redirect_url)
256
+ respond_to do |format|
257
+ format.html do
258
+ Avo.error_manager.add t("avo.not_authorized")
259
+ render html: "", layout: "avo/application", status: :forbidden
260
+ end
261
+ format.any { head :forbidden }
262
+ end
258
263
  end
259
264
 
260
265
  def set_authorization
@@ -1,6 +1,8 @@
1
1
  import { AttributeObserver, Controller } from '@hotwired/stimulus'
2
2
  import { setSelection, takeSelection } from '../index_state'
3
3
 
4
+ const MAX_ACTION_URL_LENGTH = 6000
5
+
4
6
  export default class extends Controller {
5
7
  static targets = [
6
8
  'itemCheckbox',
@@ -24,6 +26,10 @@ export default class extends Controller {
24
26
  this.selectedResourcesObserver = new AttributeObserver(this.element, 'data-selected-resources', this)
25
27
  this.selectedResourcesObserver.start()
26
28
 
29
+ // The action links carry the index query from the start, so an action's fields and messages can read it
30
+ // even when nothing is selected.
31
+ this.updateLinks('resourceIds')
32
+
27
33
  // Wait for the rows' item-selector controllers to connect; they apply the selection.
28
34
  if (rememberedIds.length > 0) {
29
35
  Promise.resolve().then(() => this.restoreSelection(rememberedIds))
@@ -58,7 +64,8 @@ export default class extends Controller {
58
64
  if (!this.hasCheckboxTarget) return
59
65
 
60
66
  // If all are selected, mark the checkbox as checked.
61
- const rowCount = this.element.querySelectorAll('tbody tr').length
67
+ // Table rows and grid cards both carry an item checkbox.
68
+ const rowCount = this.itemCheckboxTargets.length
62
69
  // Reset the checkbox
63
70
  this.checkboxTarget.indeterminate = false
64
71
  this.checkboxTarget.checked = false
@@ -151,11 +158,19 @@ export default class extends Controller {
151
158
  url.searchParams.set('fields[avo_resource_ids]', resourceIds)
152
159
  url.searchParams.set('fields[avo_selected_all]', 'false')
153
160
  } else if (param === 'selectedQuery') {
154
- const selectedQuery = this.element.dataset.itemSelectAllSelectedAllQueryValue
155
- url.searchParams.set('fields[avo_index_query]', selectedQuery)
156
161
  url.searchParams.set('fields[avo_selected_all]', 'true')
157
162
  }
158
163
 
164
+ if (this.indexQueryAvailable()) {
165
+ url.searchParams.set('fields[avo_index_query]', this.selectedAllQueryValue)
166
+
167
+ // Puma and nginx reject request lines around 8-10 KB. Only select all needs the query to open the
168
+ // modal; without it, the modal texts see no index_query and handle still gets it from the form.
169
+ if (param !== 'selectedQuery' && url.toString().length > MAX_ACTION_URL_LENGTH) {
170
+ url.searchParams.delete('fields[avo_index_query]')
171
+ }
172
+ }
173
+
159
174
  link.href = url.toString()
160
175
  } catch (error) {
161
176
  console.error('Error updating link:', link, error)
@@ -179,6 +194,11 @@ export default class extends Controller {
179
194
 
180
195
  // True if there are more pages available and if query encryption run successfully
181
196
  selectAllEnabled() {
182
- return this.pageCountValue > 1 && this.selectedAllQueryValue !== 'select_all_disabled'
197
+ return this.pageCountValue > 1 && this.indexQueryAvailable()
198
+ }
199
+
200
+ // A view type renders 'select_all_disabled' when it can't encrypt the query, and a custom one may render none
201
+ indexQueryAvailable() {
202
+ return this.selectedAllQueryValue !== '' && this.selectedAllQueryValue !== 'select_all_disabled'
183
203
  }
184
204
  }
@@ -41,6 +41,7 @@ module Avo
41
41
  attr_reader :appended_turbo_streams
42
42
  attr_reader :records_to_reload
43
43
  attr_reader :query
44
+ attr_reader :index_query
44
45
 
45
46
  # TODO: find a differnet way to delegate this to the uninitialized Current variable
46
47
  delegate :context, to: Avo::Current
@@ -130,7 +131,8 @@ module Avo
130
131
  record: @record,
131
132
  view: @view,
132
133
  arguments: @arguments,
133
- query: @query
134
+ query: @query,
135
+ index_query: @index_query
134
136
  ).handle
135
137
  end
136
138
 
@@ -388,7 +390,8 @@ module Avo
388
390
  record: @record,
389
391
  view: @view,
390
392
  arguments:,
391
- query: @query
393
+ query: @query,
394
+ index_query: @index_query
392
395
  ).handle
393
396
  end
394
397
 
@@ -406,7 +409,8 @@ module Avo
406
409
  record: @record,
407
410
  view: @view,
408
411
  arguments: @arguments,
409
- query: @query
412
+ query: @query,
413
+ index_query: @index_query
410
414
  ).handle
411
415
  end
412
416
 
@@ -260,7 +260,7 @@ When an index spans multiple pages, checking "Select all" offers to select **eve
260
260
 
261
261
  - **`query` is always an array.** Even a single-record action gets `[record]`. Use `query.first` for the one-record case; don't call record methods on `query` directly. `records` is an alias.
262
262
  - **Records deleted after selection are omitted.** Another user may delete a checked record before the action request arrives. Avo drops that missing record and still passes every surviving selection to `handle`; action authors do not need to rescue `ActiveRecord::RecordNotFound` for this race. `query` can therefore come back **empty** when every selected record is gone — guard with `return error "No record selected" if query.blank?` if that matters.
263
- - **"My action doesn't show up" is usually the policy.** With Pundit, `act_on?` in the resource's policy gates action visibility (and `authorize` on the action gates it further). Check the policy first. See the **`avo-authorization`** skill.
263
+ - **"My action doesn't show up", or is refused, is usually the policy.** With Pundit, `act_on?` in the resource's policy gates the Actions dropdown and every run: Avo asks it of the record in the URL and of each selected record (of the model class when there are none, and for a "Select all" run), and one refusal refuses the whole run. An action started from an association panel shows under the parent's `act_on_<assoc>?`, but still runs only on children whose own `act_on?` allows it. `authorize` on the action gates it further. Check the policy first. See the **`avo-authorization`** skill.
264
264
  - **`view` is the view the action was started from.** In `authorize`, `visible`, the modal text blocks, `fields`, and `handle`, `view` (and `resource.view`) is `index`, `show`, or `edit` — the page the user opened the action from — both while the modal renders and while the action runs. It is never `new`; the modal is not a view. It arrives with the request and is checked against the route (`show` and `edit` need a record in the URL), so use it to adapt behavior (`reload_records if view.index?`), never as the only gate on who may run the action: gate people on `current_user`, and single-versus-bulk on `query.size` or `record`.
265
265
  - **Every field in `fields` renders in the modal.** `hide_on`/`show_on` marks are for resource views and are ignored here. A field with a form component (text, select, hidden...) renders as an input; a display-only field (badge, key/value) renders read-only, as on Show. The one mark that counts: a badge declared with `show_on: :forms` renders as a select. The modal focuses its first input; a field with `autofocus: true` takes the focus instead (avo-fields lists the fields that support it). Use it to show context next to the inputs.
266
266
  - **The modal is a NEW request.** Params from the Index/Show page that opened it are **not** available in `fields`/`handle`. To prefill from the triggering page, parse `request.referer`:
@@ -221,7 +221,7 @@ A license authorizes **one app, one production URL** (`Rails.env.production?`).
221
221
 
222
222
  Verify at the **status page**: `https://yourapp.com/<mount-path>/avo_private/status` (e.g. `.../avo/avo_private/status` or `.../admin/avo_private/status`). It shows whether the license authenticated and what the checking server returned; the key is masked unless you set `config.exclude_from_status = []`. The viewing user must be an Avo admin. Deep failure diagnosis (unlicensed after deploy, timeouts, test-suite blocking the check host) is the **avo-troubleshoot** skill.
223
223
 
224
- **Trials are full licenses.** Every add-on a trial covers behaves exactly as it does on a paid license while the trial runs — nothing is gated or degraded. The **Avo Status** indicator in the sidebar footer is green when all is well, **amber** when a trial needs attention, and orange when the license is invalid. Amber is not a failure; it means either no payment method is on file (access stops on the trial's end date) or the subscription behind the trial was cancelled (access stops on the cancellation date, and adding a payment method won't change that — the subscription has to be resumed). Both are fixed on https://avohq.io/licenses, and the status page names which one applies. Avo caches the license response between checks, so press **Refresh license** on the status page afterwards or the app keeps showing the older billing state. Trial state needs `avo-licensing` **4.0.10+**; the amber indicator needs Avo **4.0.20+** (an older Avo renders an uncolored dot rather than breaking).
224
+ **Trials are full licenses.** Every add-on a trial covers behaves exactly as it does on a paid license while the trial runs — nothing is gated or degraded. The **Avo Status** indicator in the sidebar footer is green when all is well, **amber** when a trial needs attention, and orange when the license is invalid. Amber is not a failure: the status page names why, and the fix is on https://avohq.io/licenses. The **avo-troubleshoot** skill lists each case and its fix. Avo caches the license response between checks, so press **Refresh license** on the status page afterwards or the app keeps showing the older billing state. Trial state needs `avo-licensing` **4.0.10+**; the amber indicator needs Avo **4.0.20+** (an older Avo renders an uncolored dot rather than breaking).
225
225
 
226
226
  ### 7. (Optional) Append your own routes inside the engine
227
227
 
@@ -210,7 +210,12 @@ self.search = {
210
210
  1. **Key not set on the server.** The most frequent cause. Confirm `config.license_key = ENV["AVO_LICENSE_KEY"]` and that the env var is actually present in **production** (not just locally).
211
211
  2. **Check the status page.** Every Avo app exposes `https://yourapp.com/<mount>/avo_private/status` — e.g. `/admin/avo_private/status` if you mounted Avo at `admin`. It shows whether the license authenticated and the raw response from the check server. The viewing user must be an Avo admin.
212
212
  3. **Key hidden on the status page.** The key is redacted by default; set `exclude_from_status = []` in the initializer if you need to see it while debugging.
213
- 4. **The sidebar indicator is amber, not orange.** That is not a license failure — it is a running trial that needs attention: either no payment method is on file, or the subscription behind the trial was cancelled. Access continues until the date the status page reports. Both are fixed on https://avohq.io/licenses (a cancelled subscription must be resumed — adding a payment method won't help), then press **Refresh license** on the status page, since Avo caches the license response between checks.
213
+ 4. **The sidebar indicator is amber, not orange.** That is not a license failure — it is a running trial that needs attention, and the status page shows an **Action needed before <date>** tag below the license name, the date being when access stops. Access continues until the date the status page reports. Each case is fixed on https://avohq.io/licenses:
214
+ - No payment method is on file: add one.
215
+ - The subscription behind the trial was cancelled: resume it. A payment method won't help.
216
+ - An Avo 4 trial runs beside an Avo 3 subscription: press **Migrate to Avo 4** before the trial's end date, or Avo 4 stops working in the app when the trial ends (the Avo 3 license stays active, so the app can move back to Avo 3). A payment method won't help. An older `avo-licensing` shows add-a-payment-method steps for this case; migrating is still the fix.
217
+
218
+ Then press **Refresh license** on the status page, since Avo caches the license response between checks.
214
219
 
215
220
  → `license-troubleshooting.html`
216
221
 
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "4.3.8" unless const_defined?(:VERSION)
2
+ VERSION = "4.3.10" unless const_defined?(:VERSION)
3
3
  end
@@ -1,5 +1,19 @@
1
- desc "Runs the update command for all Avo gems."
1
+ require "open3"
2
+
3
+ desc "Updates all Avo gems; set AVO_UPDATE_PUSH=1 to commit Gemfile.lock and push."
2
4
  task "avo:update" => :environment do
5
+ push = ENV["AVO_UPDATE_PUSH"] == "1"
6
+ lockfile = Bundler.default_lockfile.to_s
7
+
8
+ if push
9
+ status, result = Open3.capture2("git", "status", "--porcelain", "--untracked-files=no")
10
+ abort "[Avo->] AVO_UPDATE_PUSH requires a Git repository." unless result.success?
11
+ abort "[Avo->] AVO_UPDATE_PUSH requires no changes to tracked files." unless status.empty?
12
+ unless system("git", "rev-parse", "--verify", "@{upstream}", out: File::NULL, err: File::NULL)
13
+ abort "[Avo->] AVO_UPDATE_PUSH requires a branch with an existing upstream."
14
+ end
15
+ end
16
+
3
17
  # Start with avo core (it doesn't register itself as a plugin), then resolve
4
18
  # every registered plugin to its real gem name. Plugins register under
5
19
  # nicknames (e.g. `avo-rhino_field` registers as `:rhino`), so we ask each
@@ -20,7 +34,21 @@ task "avo:update" => :environment do
20
34
  # `bundle exec` settings (BUNDLE_GEMFILE, BUNDLE_FROZEN/--deployment, RUBYOPT),
21
35
  # which can make the update silently refuse. Exported credentials survive
22
36
  # because they're part of the original env Bundler snapshotted at boot.
23
- Bundler.with_original_env { system cmd }
37
+ abort "[Avo->] Gem update failed; nothing committed or pushed." unless Bundler.with_original_env { system cmd }
38
+
39
+ if push
40
+ abort "[Avo->] Could not stage #{lockfile}; nothing pushed." unless system("git", "add", "--", lockfile)
41
+ _, result = Open3.capture2("git", "diff", "--cached", "--quiet", "--", lockfile)
42
+ if result.success?
43
+ puts "[Avo->] Gemfile.lock is unchanged; nothing committed or pushed."
44
+ next
45
+ end
46
+ abort "[Avo->] Could not inspect Gemfile.lock; nothing pushed." unless result.exitstatus == 1
47
+ unless system("git", "commit", "--only", "-m", "Update Avo gems", "--", lockfile)
48
+ abort "[Avo->] Commit failed; nothing pushed."
49
+ end
50
+ abort "[Avo->] Push failed; the update commit is available locally." unless system("git", "push")
51
+ end
24
52
  end
25
53
 
26
54
  desc "Builds Avo (just assets for now)"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avo
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.3.8
4
+ version: 4.3.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin