spree_core 5.4.3 → 5.5.0.rc2
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.
- checksums.yaml +4 -4
- data/app/helpers/spree/base_helper.rb +0 -82
- data/app/helpers/spree/currency_helper.rb +0 -12
- data/app/helpers/spree/products_helper.rb +0 -8
- data/app/jobs/spree/base_job.rb +18 -0
- data/app/jobs/spree/events/subscriber_job.rb +2 -1
- data/app/jobs/spree/exports/generate_job.rb +11 -0
- data/app/jobs/spree/images/save_from_url_job.rb +23 -8
- data/app/jobs/spree/imports/assign_tags_job.rb +11 -0
- data/app/jobs/spree/imports/base_job.rb +15 -0
- data/app/jobs/spree/imports/create_categories_job.rb +37 -0
- data/app/jobs/spree/imports/create_rows_job.rb +1 -3
- data/app/jobs/spree/imports/process_group_job.rb +8 -6
- data/app/jobs/spree/imports/process_rows_job.rb +1 -3
- data/app/jobs/spree/media/migrate_product_assets_job.rb +83 -0
- data/app/jobs/spree/products/refresh_metrics_job.rb +15 -4
- data/app/jobs/spree/reports/generate_job.rb +11 -0
- data/app/jobs/spree/search_provider/index_job.rb +5 -1
- data/app/jobs/spree/search_provider/remove_job.rb +4 -0
- data/app/jobs/spree/stock_reservations/expire_job.rb +11 -0
- data/app/models/concerns/spree/calculated_adjustments.rb +34 -1
- data/app/models/concerns/spree/display_on.rb +31 -0
- data/app/models/concerns/spree/metafields.rb +167 -5
- data/app/models/concerns/spree/preference_schema.rb +191 -0
- data/app/models/concerns/spree/prefixed_id.rb +94 -11
- data/app/models/concerns/spree/product_scopes.rb +36 -17
- data/app/models/concerns/spree/ransackable_attributes.rb +5 -1
- data/app/models/concerns/spree/search_indexable.rb +8 -7
- data/app/models/concerns/spree/searchable.rb +11 -2
- data/app/models/concerns/spree/stores/channels.rb +20 -0
- data/app/models/concerns/spree/stores/markets.rb +21 -5
- data/app/models/concerns/spree/typed_associations.rb +120 -0
- data/app/models/concerns/spree/user_methods.rb +71 -12
- data/app/models/spree/ability.rb +4 -117
- data/app/models/spree/api_key.rb +60 -0
- data/app/models/spree/asset.rb +28 -5
- data/app/models/spree/authentication/strategy_registry.rb +72 -0
- data/app/models/spree/base.rb +18 -1
- data/app/models/spree/channel.rb +159 -0
- data/app/models/spree/country.rb +2 -0
- data/app/models/spree/current.rb +7 -3
- data/app/models/spree/custom_field.rb +9 -0
- data/app/models/spree/custom_field_definition.rb +7 -0
- data/app/models/spree/customer_group.rb +8 -2
- data/app/models/spree/export.rb +45 -3
- data/app/models/spree/exports/coupon_codes.rb +4 -0
- data/app/models/spree/exports/newsletter_subscribers.rb +4 -0
- data/app/models/spree/exports/product_translations.rb +4 -0
- data/app/models/spree/gateway.rb +25 -0
- data/app/models/spree/gift_card.rb +1 -1
- data/app/models/spree/gift_card_batch.rb +4 -1
- data/app/models/spree/import.rb +5 -0
- data/app/models/spree/import_row.rb +12 -0
- data/app/models/spree/line_item.rb +6 -1
- data/app/models/spree/market.rb +32 -1
- data/app/models/spree/metafield.rb +38 -0
- data/app/models/spree/metafield_definition.rb +29 -6
- data/app/models/spree/metafields/json.rb +10 -0
- data/app/models/spree/newsletter_subscriber.rb +19 -3
- data/app/models/spree/option_type.rb +48 -7
- data/app/models/spree/order/checkout.rb +3 -3
- data/app/models/spree/order.rb +102 -6
- data/app/models/spree/order_approval.rb +19 -0
- data/app/models/spree/order_cancellation.rb +19 -0
- data/app/models/spree/order_routing/has_strategy_preference.rb +28 -0
- data/app/models/spree/order_routing/rules/default_location.rb +16 -0
- data/app/models/spree/order_routing/rules/minimize_splits.rb +45 -0
- data/app/models/spree/order_routing/rules/preferred_location.rb +22 -0
- data/app/models/spree/order_routing/strategy/base.rb +47 -0
- data/app/models/spree/order_routing/strategy/legacy.rb +33 -0
- data/app/models/spree/order_routing/strategy/reducer.rb +68 -0
- data/app/models/spree/order_routing/strategy/rules.rb +83 -0
- data/app/models/spree/order_routing_rule.rb +75 -0
- data/app/models/spree/permission_sets/configuration_management.rb +16 -0
- data/app/models/spree/permission_sets/product_display.rb +2 -0
- data/app/models/spree/permission_sets/product_management.rb +2 -0
- data/app/models/spree/price.rb +14 -1
- data/app/models/spree/price_list.rb +129 -17
- data/app/models/spree/price_rule.rb +11 -1
- data/app/models/spree/price_rules/customer_group_rule.rb +15 -1
- data/app/models/spree/price_rules/market_rule.rb +16 -1
- data/app/models/spree/price_rules/user_rule.rb +21 -2
- data/app/models/spree/product/channels.rb +149 -0
- data/app/models/spree/product/legacy_multi_store_support.rb +40 -0
- data/app/models/spree/product/slugs.rb +1 -1
- data/app/models/spree/product.rb +172 -31
- data/app/models/spree/product_publication.rb +43 -0
- data/app/models/spree/promotion/actions/create_adjustment.rb +4 -0
- data/app/models/spree/promotion/actions/create_item_adjustments.rb +4 -0
- data/app/models/spree/promotion/actions/create_line_items.rb +32 -14
- data/app/models/spree/promotion/rules/country.rb +40 -18
- data/app/models/spree/promotion/rules/customer_group.rb +10 -1
- data/app/models/spree/promotion/rules/product.rb +4 -0
- data/app/models/spree/promotion/rules/taxon.rb +24 -1
- data/app/models/spree/promotion/rules/user.rb +21 -0
- data/app/models/spree/promotion/rules/user_logged_in.rb +6 -0
- data/app/models/spree/promotion.rb +22 -1
- data/app/models/spree/promotion_action.rb +17 -11
- data/app/models/spree/promotion_rule.rb +17 -18
- data/app/models/spree/search_provider/meilisearch.rb +12 -2
- data/app/models/spree/stock/availability_validator.rb +1 -1
- data/app/models/spree/stock/quantifier.rb +89 -9
- data/app/models/spree/stock_item.rb +36 -0
- data/app/models/spree/stock_location.rb +52 -0
- data/app/models/spree/stock_reservation.rb +38 -0
- data/app/models/spree/stock_reservations/insufficient_stock_error.rb +12 -0
- data/app/models/spree/store.rb +18 -72
- data/app/models/spree/store_credit.rb +0 -8
- data/app/models/spree/store_product.rb +11 -23
- data/app/models/spree/taxon.rb +0 -5
- data/app/models/spree/user_identity.rb +1 -2
- data/app/models/spree/variant.rb +132 -18
- data/app/models/spree/variant_media.rb +46 -0
- data/app/models/spree/webhook_delivery.rb +1 -1
- data/app/models/spree/webhook_endpoint.rb +24 -0
- data/app/models/spree/wished_item.rb +0 -13
- data/app/presenters/spree/csv/product_variant_presenter.rb +23 -3
- data/app/presenters/spree/search_provider/product_presenter.rb +11 -4
- data/app/presenters/spree/variant_presenter.rb +4 -3
- data/app/services/spree/addresses/update.rb +6 -8
- data/app/services/spree/cart/add_item.rb +10 -0
- data/app/services/spree/cart/empty.rb +2 -0
- data/app/services/spree/cart/remove_line_item.rb +10 -0
- data/app/services/spree/cart/remove_out_of_stock_items.rb +1 -1
- data/app/services/spree/cart/set_quantity.rb +10 -0
- data/app/services/spree/carts/complete.rb +1 -0
- data/app/services/spree/carts/create.rb +1 -0
- data/app/services/spree/carts/update.rb +18 -2
- data/app/services/spree/carts/upsert_items.rb +6 -6
- data/app/services/spree/imports/row_processors/customer.rb +4 -1
- data/app/services/spree/imports/row_processors/product_variant.rb +95 -57
- data/app/services/spree/newsletter/link_user.rb +53 -0
- data/app/services/spree/newsletter/subscribe.rb +31 -9
- data/app/services/spree/orders/approve.rb +27 -6
- data/app/services/spree/orders/build_shipments.rb +29 -0
- data/app/services/spree/orders/cancel.rb +34 -3
- data/app/services/spree/orders/complete.rb +53 -0
- data/app/services/spree/orders/create.rb +156 -0
- data/app/services/spree/orders/update.rb +51 -0
- data/app/services/spree/orders/upsert_items.rb +70 -0
- data/app/services/spree/prices/bulk_upsert.rb +201 -0
- data/app/services/spree/products/duplicator.rb +1 -1
- data/app/services/spree/products/prepare_nested_attributes.rb +2 -30
- data/app/services/spree/sample_data/loader.rb +30 -0
- data/app/services/spree/stock_reservations/extend.rb +19 -0
- data/app/services/spree/stock_reservations/release.rb +12 -0
- data/app/services/spree/stock_reservations/reserve.rb +103 -0
- data/app/services/spree/taxons/remove_products.rb +7 -1
- data/app/subscribers/spree/product_metrics_subscriber.rb +3 -7
- data/app/views/spree/invitation_mailer/invitation_email.html.erb +4 -0
- data/config/locales/en.yml +28 -10
- data/config/routes.rb +9 -0
- data/db/migrate/20260429000001_create_spree_order_cancellations.rb +25 -0
- data/db/migrate/20260429000002_create_spree_order_approvals.rb +22 -0
- data/db/migrate/20260429000003_add_status_to_spree_orders.rb +6 -0
- data/db/migrate/20260429000004_add_scopes_to_spree_api_keys.rb +11 -0
- data/db/migrate/20260501000001_create_spree_stock_reservations.rb +19 -0
- data/db/migrate/20260507162651_create_spree_variant_media.rb +23 -0
- data/db/migrate/20260508175303_add_pickup_to_spree_stock_locations.rb +12 -0
- data/db/migrate/20260508204040_create_spree_channels.rb +18 -0
- data/db/migrate/20260508204041_create_spree_order_routing_rules.rb +18 -0
- data/db/migrate/20260508204042_add_preferred_stock_location_to_spree_orders.rb +5 -0
- data/db/migrate/20260508204043_add_channel_id_to_spree_orders.rb +10 -0
- data/db/migrate/20260511000001_backfill_status_on_spree_orders.rb +57 -0
- data/db/migrate/20260515000001_add_store_id_to_spree_newsletter_subscribers.rb +25 -0
- data/db/migrate/20260529000001_add_unique_index_to_spree_price_rules.rb +41 -0
- data/db/migrate/20260529000002_add_unique_index_to_spree_promotion_rules.rb +37 -0
- data/db/migrate/20260601000001_create_spree_product_publications.rb +14 -0
- data/db/migrate/20260601000002_add_store_id_to_spree_products.rb +16 -0
- data/db/migrate/20260602000001_add_default_to_spree_channels.rb +14 -0
- data/db/migrate/20260612000001_change_spree_user_identities_info_to_jsonb.rb +13 -0
- data/db/sample_data/channels.rb +12 -0
- data/db/sample_data/orders.rb +1 -1
- data/db/sample_data/products.csv +212 -212
- data/lib/generators/spree/api_resource/api_resource_generator.rb +353 -0
- data/lib/generators/spree/api_resource/templates/admin_controller.rb.tt +23 -0
- data/lib/generators/spree/api_resource/templates/admin_controller_spec.rb.tt +59 -0
- data/lib/generators/spree/api_resource/templates/admin_serializer.rb.tt +11 -0
- data/lib/generators/spree/api_resource/templates/factory.rb.tt +26 -0
- data/lib/generators/spree/api_resource/templates/store_aliased_serializer.rb.tt +12 -0
- data/lib/generators/spree/api_resource/templates/store_controller.rb.tt +31 -0
- data/lib/generators/spree/api_resource/templates/store_controller_spec.rb.tt +61 -0
- data/lib/generators/spree/api_resource/templates/store_serializer.rb.tt +17 -0
- data/lib/generators/spree/controller_decorator/controller_decorator_generator.rb +66 -0
- data/lib/generators/spree/controller_decorator/templates/controller_decorator.rb.tt +25 -0
- data/lib/generators/spree/model/model_generator.rb +73 -7
- data/lib/generators/spree/model/templates/create_table_migration.rb.tt +40 -0
- data/lib/generators/spree/model/templates/model.rb.tt +28 -2
- data/lib/generators/spree/subscriber/subscriber_generator.rb +116 -0
- data/lib/generators/spree/subscriber/templates/subscriber.rb.tt +17 -0
- data/lib/generators/spree/subscriber/templates/subscriber_spec.rb.tt +9 -0
- data/lib/spree/core/configuration.rb +7 -0
- data/lib/spree/core/controller_helpers/auth.rb +0 -12
- data/lib/spree/core/controller_helpers/currency.rb +0 -17
- data/lib/spree/core/controller_helpers/order.rb +0 -19
- data/lib/spree/core/dependencies.rb +5 -2
- data/lib/spree/core/engine.rb +54 -7
- data/lib/spree/core/permission_configuration.rb +15 -0
- data/lib/spree/core/preferences/masking.rb +47 -0
- data/lib/spree/core/preferences/preferable_class_methods.rb +7 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +56 -5
- data/lib/spree/permitted_attributes.rb +9 -7
- data/lib/spree/testing_support/factories/address_factory.rb +16 -9
- data/lib/spree/testing_support/factories/api_key_factory.rb +1 -0
- data/lib/spree/testing_support/factories/channel_factory.rb +8 -0
- data/lib/spree/testing_support/factories/line_item_factory.rb +2 -8
- data/lib/spree/testing_support/factories/newsletter_subscriber_factory.rb +2 -0
- data/lib/spree/testing_support/factories/product_factory.rb +16 -7
- data/lib/spree/testing_support/factories/product_publication_factory.rb +6 -0
- data/lib/spree/testing_support/factories/refresh_token_factory.rb +15 -0
- data/lib/spree/testing_support/factories/stock_location_factory.rb +2 -2
- data/lib/spree/testing_support/factories/stock_reservation_factory.rb +31 -0
- data/lib/spree/testing_support/factories/variant_factory.rb +3 -3
- data/lib/spree/testing_support/order_walkthrough.rb +1 -1
- data/lib/spree/testing_support/store.rb +10 -0
- data/lib/spree/upgrades/5_4_to_5_5/manifest.yml +53 -0
- data/lib/tasks/channels.rake +94 -0
- data/lib/tasks/cli.rake +2 -1
- data/lib/tasks/core.rake +1 -0
- data/lib/tasks/media.rake +27 -0
- data/lib/tasks/products.rake +4 -6
- data/lib/tasks/publications.rake +60 -0
- data/lib/tasks/upgrade.rake +211 -0
- metadata +87 -18
- data/app/finders/spree/variants/visible_finder.rb +0 -23
- data/app/paginators/spree/shared/paginate.rb +0 -30
- data/app/presenters/spree/filters/price_presenter.rb +0 -23
- data/app/presenters/spree/filters/price_range_presenter.rb +0 -30
- data/app/presenters/spree/filters/quantified_price_range_presenter.rb +0 -45
- data/app/presenters/spree/product_summary_presenter.rb +0 -27
- data/app/presenters/spree/variants/options_presenter.rb +0 -82
- data/app/services/spree/classifications/reposition.rb +0 -23
- data/app/sorters/spree/orders/sort.rb +0 -10
- data/lib/spree/core/controller_helpers/common.rb +0 -14
- data/lib/spree/core/token_generator.rb +0 -23
- data/lib/spree/database_type_utilities.rb +0 -22
- data/lib/spree/testing_support/bar_ability.rb +0 -14
- data/lib/spree/testing_support/factories/store_product_factory.rb +0 -6
data/lib/tasks/core.rake
CHANGED
data/lib/tasks/media.rake
CHANGED
|
@@ -16,5 +16,32 @@ namespace :spree do
|
|
|
16
16
|
|
|
17
17
|
puts 'Done!'
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
# Enqueues Spree::Media::MigrateProductAssetsJob for every product that
|
|
21
|
+
# still has at least one variant-pinned asset. The job is idempotent, so
|
|
22
|
+
# re-running this task is safe.
|
|
23
|
+
#
|
|
24
|
+
# ENV vars:
|
|
25
|
+
# BATCH_SIZE — products fetched per scope batch (default: 500)
|
|
26
|
+
desc 'Enqueue jobs to migrate legacy variant-pinned images to product-level media (opt-in, 5.5)'
|
|
27
|
+
task migrate_master_images_to_product_media: :environment do
|
|
28
|
+
batch_size = ENV.fetch('BATCH_SIZE', 500).to_i
|
|
29
|
+
batch_size = 500 if batch_size < 1
|
|
30
|
+
|
|
31
|
+
# Subquery (not pluck) so the product set doesn't materialize in Ruby —
|
|
32
|
+
# important for catalogs with millions of products.
|
|
33
|
+
variant_product_ids = Spree::Variant
|
|
34
|
+
.joins("INNER JOIN #{Spree::Asset.table_name} ON " \
|
|
35
|
+
"#{Spree::Asset.table_name}.viewable_id = #{Spree::Variant.table_name}.id " \
|
|
36
|
+
"AND #{Spree::Asset.table_name}.viewable_type = 'Spree::Variant'")
|
|
37
|
+
.select(:product_id)
|
|
38
|
+
|
|
39
|
+
relation = Spree::Product.where(id: variant_product_ids)
|
|
40
|
+
relation.find_each(batch_size: batch_size) do |product|
|
|
41
|
+
Spree::Media::MigrateProductAssetsJob.perform_later(product.id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
puts "Enqueued migration jobs for #{relation.count} products on the #{Spree.queues.images} queue."
|
|
45
|
+
end
|
|
19
46
|
end
|
|
20
47
|
end
|
data/lib/tasks/products.rake
CHANGED
|
@@ -19,21 +19,19 @@ namespace :spree do
|
|
|
19
19
|
puts "\nDone!"
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
desc 'Enqueue background jobs to populate product metrics for
|
|
22
|
+
desc 'Enqueue background jobs to populate product metrics for every product'
|
|
23
23
|
task populate_metrics: :environment do
|
|
24
24
|
total_count = 0
|
|
25
25
|
|
|
26
|
-
Spree::
|
|
27
|
-
jobs = batch.pluck(:
|
|
28
|
-
Spree::Products::RefreshMetricsJob.new(product_id, store_id)
|
|
29
|
-
end
|
|
26
|
+
Spree::Product.in_batches(of: 100) do |batch|
|
|
27
|
+
jobs = batch.pluck(:id).map { |product_id| Spree::Products::RefreshMetricsJob.new(product_id) }
|
|
30
28
|
ActiveJob.perform_all_later(jobs)
|
|
31
29
|
total_count += jobs.size
|
|
32
30
|
print '.'
|
|
33
31
|
end
|
|
34
32
|
|
|
35
33
|
if total_count.zero?
|
|
36
|
-
puts 'No
|
|
34
|
+
puts 'No products found.'
|
|
37
35
|
else
|
|
38
36
|
puts "\nEnqueued #{total_count} jobs to refresh product metrics."
|
|
39
37
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
namespace :spree do
|
|
2
|
+
namespace :upgrade do
|
|
3
|
+
desc <<~DESC
|
|
4
|
+
Populates +spree_products.store_id+ and +spree_product_publications+ from the legacy
|
|
5
|
+
+spree_products_stores+ join. Idempotent — re-running skips products that
|
|
6
|
+
already have a +store_id+ and channels that already have a publication for
|
|
7
|
+
the product.
|
|
8
|
+
|
|
9
|
+
Run once after upgrading to Spree 5.5+. Multi-store merchants must install
|
|
10
|
+
+spree_multi_store+ before running; running on a multi-store catalog without
|
|
11
|
+
the extension picks the earliest +spree_products_stores+ row (by
|
|
12
|
+
+created_at+) as the product's home store.
|
|
13
|
+
DESC
|
|
14
|
+
task populate_publications: :environment do
|
|
15
|
+
unless ActiveRecord::Base.connection.table_exists?(Spree::StoreProduct.table_name)
|
|
16
|
+
puts " #{Spree::StoreProduct.table_name} table not found — nothing to migrate."
|
|
17
|
+
next
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
batch_size = (ENV['BATCH_SIZE'] || 1_000).to_i
|
|
21
|
+
publications_created = 0
|
|
22
|
+
|
|
23
|
+
# Pass 1: per store, batch-publish products onto the store's default
|
|
24
|
+
# channel via +Channel#add_products+. One upsert + one touch_all per
|
|
25
|
+
# batch beats the previous per-product loop by orders of magnitude on
|
|
26
|
+
# large catalogs. +add_products+ is upsert-based with +on_duplicate:
|
|
27
|
+
# :skip+, so existing publications on re-run are no-ops.
|
|
28
|
+
Spree::Store.find_each do |store|
|
|
29
|
+
channel = store.default_channel
|
|
30
|
+
unless channel
|
|
31
|
+
puts " Store '#{store.name}' has no default channel — skipping."
|
|
32
|
+
next
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
store_publications = 0
|
|
36
|
+
Spree::StoreProduct.where(store_id: store.id).in_batches(of: batch_size) do |batch|
|
|
37
|
+
store_publications += channel.add_products(batch.pluck(:product_id))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
publications_created += store_publications
|
|
41
|
+
puts " Store '#{store.name}': created #{store_publications} publication(s)" if store_publications.positive?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Pass 2: assign +store_id+ on products that still don't have one,
|
|
45
|
+
# using the earliest legacy row per product.
|
|
46
|
+
products_processed = 0
|
|
47
|
+
|
|
48
|
+
Spree::Product.where(store_id: nil).find_each(batch_size: batch_size) do |product|
|
|
49
|
+
store_id = Spree::StoreProduct.where(product_id: product.id).order(:created_at).limit(1).pick(:store_id)
|
|
50
|
+
next unless store_id
|
|
51
|
+
|
|
52
|
+
product.update_column(:store_id, store_id)
|
|
53
|
+
products_processed += 1
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
puts " Processed #{products_processed} products"
|
|
57
|
+
puts " Created #{publications_created} publications"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# spree:upgrade — runs the data-backfill rake tasks shipped with the
|
|
4
|
+
# currently-installed spree_core. Intended to run on production (or any
|
|
5
|
+
# environment) after `bundle update` + `db:migrate` have already happened
|
|
6
|
+
# in your deploy pipeline.
|
|
7
|
+
#
|
|
8
|
+
# Walks EVERY upgrade manifest whose `to` is ≤ the installed minor version,
|
|
9
|
+
# in ascending order. So an app that's been on 5.3 and jumps straight to
|
|
10
|
+
# 5.5 still gets the 5.3→5.4 data backfills before the 5.4→5.5 ones.
|
|
11
|
+
# Every step in every manifest is required to be idempotent — re-running
|
|
12
|
+
# the task on an already-upgraded app is a safe no-op.
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# bundle exec rake spree:upgrade # walk all eligible manifests
|
|
16
|
+
# bundle exec rake spree:upgrade DRY_RUN=1 # print plan, run nothing
|
|
17
|
+
# bundle exec rake spree:upgrade STEP=channels # run one step by id (any manifest)
|
|
18
|
+
# bundle exec rake spree:upgrade TO=5.4 # cap the walk at this version
|
|
19
|
+
#
|
|
20
|
+
# Manifests live alongside this file at lib/spree/upgrades/<from>_to_<to>/
|
|
21
|
+
# manifest.yml and ship inside the spree_core gem.
|
|
22
|
+
|
|
23
|
+
require 'yaml'
|
|
24
|
+
|
|
25
|
+
namespace :spree do
|
|
26
|
+
desc 'Run the post-deploy upgrade tasks for the installed Spree version'
|
|
27
|
+
task upgrade: :environment do
|
|
28
|
+
Spree::Upgrade::Runner.new(
|
|
29
|
+
target_version: ENV['TO'],
|
|
30
|
+
step_id: ENV['STEP'],
|
|
31
|
+
dry_run: ENV['DRY_RUN'] == '1'
|
|
32
|
+
).call
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
module Spree
|
|
37
|
+
module Upgrade
|
|
38
|
+
# Two-segment "5.5" form of the installed Spree version.
|
|
39
|
+
def self.installed_minor_version
|
|
40
|
+
Spree.version.split('.').first(2).join('.')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Root directory containing N_M_to_O_P/manifest.yml files inside
|
|
44
|
+
# the spree_core gem.
|
|
45
|
+
def self.manifests_root
|
|
46
|
+
File.expand_path('../spree/upgrades', __dir__)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# All available manifest directories, parsed into { from:, to:, dir: }.
|
|
50
|
+
# Sorted by `to` ascending, with `from` as a tiebreaker (smallest first)
|
|
51
|
+
# for the rare case where two manifests share a `to` boundary.
|
|
52
|
+
def self.available_manifests
|
|
53
|
+
Dir.glob(File.join(manifests_root, '*_to_*')).filter_map do |dir|
|
|
54
|
+
name = File.basename(dir)
|
|
55
|
+
match = name.match(/\A([\d_]+)_to_([\d_]+)\z/)
|
|
56
|
+
next unless match
|
|
57
|
+
|
|
58
|
+
{ from: match[1].tr('_', '.'), to: match[2].tr('_', '.'), dir: dir }
|
|
59
|
+
end.sort_by { |m| version_parts(m[:to]) + version_parts(m[:from]) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.version_parts(v)
|
|
63
|
+
v.split('.').map { |s| Integer(s, 10) rescue 0 }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Compare two dotted-version strings (returns -1, 0, +1).
|
|
67
|
+
def self.compare(a, b)
|
|
68
|
+
version_parts(a) <=> version_parts(b)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The runner is a class (not a method) so individual concerns (selection,
|
|
72
|
+
# rendering, invocation) stay separable and the plan can be inspected
|
|
73
|
+
# without execution.
|
|
74
|
+
class Runner
|
|
75
|
+
attr_reader :target_version, :step_id, :dry_run, :target_explicit
|
|
76
|
+
|
|
77
|
+
def initialize(target_version: nil, step_id: nil, dry_run: false)
|
|
78
|
+
@target_explicit = !target_version.nil?
|
|
79
|
+
@target_version = target_version || Spree::Upgrade.installed_minor_version
|
|
80
|
+
@step_id = step_id
|
|
81
|
+
@dry_run = dry_run
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def call
|
|
85
|
+
manifests = eligible_manifests
|
|
86
|
+
|
|
87
|
+
if manifests.empty?
|
|
88
|
+
puts " No upgrade manifests apply to Spree #{target_version} (installed: #{Spree::Upgrade.installed_minor_version})."
|
|
89
|
+
return
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if step_id
|
|
93
|
+
run_single_step(manifests)
|
|
94
|
+
else
|
|
95
|
+
run_full_walk(manifests)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
# In plan-mode without explicit TO, show manifests ahead of the installed
|
|
102
|
+
# version — the path the operator is about to walk. Otherwise filter to
|
|
103
|
+
# manifests whose `to` is ≤ target.
|
|
104
|
+
def eligible_manifests
|
|
105
|
+
Spree::Upgrade.available_manifests
|
|
106
|
+
.select { |manifest| manifest_eligible?(manifest) }
|
|
107
|
+
.map { |entry| load_manifest_yaml(entry) }
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def manifest_eligible?(manifest)
|
|
111
|
+
if dry_run && !target_explicit
|
|
112
|
+
Spree::Upgrade.compare(manifest[:from], Spree::Upgrade.installed_minor_version) >= 0
|
|
113
|
+
else
|
|
114
|
+
Spree::Upgrade.compare(manifest[:to], target_version) <= 0
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def load_manifest_yaml(entry)
|
|
119
|
+
YAML.safe_load_file(File.join(entry[:dir], 'manifest.yml'))
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def run_full_walk(manifests)
|
|
123
|
+
total_steps = manifests.sum { |m| m['steps'].size }
|
|
124
|
+
reported_target = if dry_run && !target_explicit
|
|
125
|
+
manifests.map { |m| m['to'] }.compact.max_by { |v| Spree::Upgrade.version_parts(v) } || target_version
|
|
126
|
+
else
|
|
127
|
+
target_version
|
|
128
|
+
end
|
|
129
|
+
puts
|
|
130
|
+
puts " Walking #{manifests.size} manifest(s), #{total_steps} step(s) total. Target: Spree #{reported_target}."
|
|
131
|
+
|
|
132
|
+
manifests.each do |manifest|
|
|
133
|
+
print_manifest_header(manifest)
|
|
134
|
+
manifest['steps'].each_with_index do |step, i|
|
|
135
|
+
print_step(step, i + 1, manifest['steps'].size)
|
|
136
|
+
unless dry_run
|
|
137
|
+
invoke(step)
|
|
138
|
+
print_step_complete(step)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
puts
|
|
144
|
+
puts dry_run ? ' (dry run — nothing executed)' : ' Upgrade tasks complete.'
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# STEP=<id> looks across every eligible manifest; we need exactly one
|
|
148
|
+
# match. Multiple matches are likely a manifest bug (two manifests
|
|
149
|
+
# referencing the same step id) but we surface it rather than picking
|
|
150
|
+
# silently.
|
|
151
|
+
def run_single_step(manifests)
|
|
152
|
+
matches = manifests.flat_map do |manifest|
|
|
153
|
+
manifest['steps'].select { |s| s['id'] == step_id }.map { |s| [manifest, s] }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
if matches.empty?
|
|
157
|
+
available = manifests.flat_map { |m| m['steps'] }.map { |s| s['id'] }.uniq.join(', ')
|
|
158
|
+
abort " No step with id '#{step_id}' in any eligible manifest. Available: #{available}"
|
|
159
|
+
elsif matches.size > 1
|
|
160
|
+
locations = matches.map { |m, _| "#{m['from']} → #{m['to']}" }.join(', ')
|
|
161
|
+
abort " Step id '#{step_id}' is ambiguous — defined in: #{locations}. Pass TO=<version> to narrow."
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
manifest, step = matches.first
|
|
165
|
+
print_manifest_header(manifest)
|
|
166
|
+
print_step(step, 1, 1)
|
|
167
|
+
unless dry_run
|
|
168
|
+
invoke(step)
|
|
169
|
+
print_step_complete(step)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
puts
|
|
173
|
+
puts dry_run ? ' (dry run — nothing executed)' : " Step '#{step_id}' complete."
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def print_manifest_header(manifest)
|
|
177
|
+
puts
|
|
178
|
+
puts " ── Spree #{manifest['from']} → #{manifest['to']} ──"
|
|
179
|
+
puts " Docs: #{manifest['docs']}" if manifest['docs']
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def print_step(step, index, total)
|
|
183
|
+
puts
|
|
184
|
+
puts " Step #{index}/#{total} [#{step['id']}]"
|
|
185
|
+
puts " #{step['name']}"
|
|
186
|
+
puts " > bin/rake #{step['task']}"
|
|
187
|
+
return unless step['notes']
|
|
188
|
+
|
|
189
|
+
step['notes'].each_line { |line| puts " #{line.chomp}" }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def print_step_complete(step)
|
|
193
|
+
puts " ✓ #{step['task']} done."
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def invoke(step)
|
|
197
|
+
task = step.fetch('task')
|
|
198
|
+
puts " Running #{task}..."
|
|
199
|
+
|
|
200
|
+
# Rake caches invoked tasks in-process; explicit reenable lets a
|
|
201
|
+
# single `rake spree:upgrade` run re-invoke aggregators that share
|
|
202
|
+
# subtasks across multiple manifests (e.g. two manifests both
|
|
203
|
+
# depending on `spree:install:migrations` would otherwise only
|
|
204
|
+
# invoke it once).
|
|
205
|
+
rake_task = Rake::Task[task]
|
|
206
|
+
rake_task.reenable
|
|
207
|
+
rake_task.invoke
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|