spree_core 6.0.0.rc6 → 6.0.0
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/jobs/spree/maintenance_tasks/run_job.rb +321 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/backfill_order_markets.rb +48 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/capture_methods.rb +40 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/deliveries.rb +15 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/order_coupon_codes.rb +15 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/promotion_option_value_rules.rb +15 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/rake_step.rb +83 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/returns.rb +20 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade/users_to_customers.rb +28 -0
- data/app/maintenance_tasks/spree/maintenance_tasks/upgrade_step.rb +44 -0
- data/app/models/spree/ability.rb +9 -0
- data/app/models/spree/maintenance_task.rb +221 -0
- data/app/models/spree/maintenance_task_run.rb +78 -0
- data/app/models/spree/upgrade_record.rb +62 -0
- data/app/services/spree/maintenance_tasks/argument_parser.rb +23 -0
- data/app/services/spree/maintenance_tasks/cancel.rb +51 -0
- data/app/services/spree/maintenance_tasks/start.rb +127 -0
- data/app/services/spree/seeds/all.rb +22 -0
- data/config/locales/en.yml +1 -0
- data/db/migrate/20260813130001_add_capture_method_to_payment_methods.rb +1 -1
- data/db/migrate/20260815000001_create_spree_maintenance_task_runs.rb +40 -0
- data/db/migrate/20260816000001_create_spree_upgrade_records.rb +17 -0
- data/lib/generators/spree/api_resource/api_resource_generator.rb +2 -2
- data/lib/spree/core/configuration.rb +6 -0
- data/lib/spree/core/engine.rb +10 -1
- data/lib/spree/core/permissions/default_catalog.rb +7 -0
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +16 -1
- data/lib/spree/testing_support/factories/maintenance_task_run_factory.rb +7 -0
- data/lib/spree/upgrade.rb +189 -0
- data/lib/spree/upgrades/5_6_to_6_0/manifest.yml +17 -10
- data/lib/tasks/maintenance_tasks.rake +42 -0
- data/lib/tasks/upgrade.rake +81 -60
- metadata +25 -6
- data/lib/tasks/capture_methods_migration.rake +0 -30
- data/lib/tasks/order_market_backfill.rake +0 -27
|
@@ -108,7 +108,7 @@ module Spree
|
|
|
108
108
|
|
|
109
109
|
# Override parent: skip if the model existed before this run. Migrations
|
|
110
110
|
# are append-only — schema changes get a separate migration:
|
|
111
|
-
# pnpm exec spree
|
|
111
|
+
# pnpm exec spree generate migration AddFooToBar foo:string
|
|
112
112
|
def create_migration_file
|
|
113
113
|
if @model_existed_before_run
|
|
114
114
|
say_status :skip, 'migration (model already exists; add a new migration for schema changes)', :yellow
|
|
@@ -295,7 +295,7 @@ module Spree
|
|
|
295
295
|
end
|
|
296
296
|
say " 4. Add `has_many :#{plural_name}` to Spree::Store" if store_scoped?
|
|
297
297
|
if options[:store] || options[:admin]
|
|
298
|
-
say " #{store_scoped? ? 5 : 4}. Run the specs: pnpm exec spree
|
|
298
|
+
say " #{store_scoped? ? 5 : 4}. Run the specs: pnpm exec spree test spec/controllers/spree/api/v3/"
|
|
299
299
|
end
|
|
300
300
|
say ''
|
|
301
301
|
end
|
|
@@ -195,6 +195,12 @@ module Spree
|
|
|
195
195
|
# imports
|
|
196
196
|
preference :large_import_threshold, :integer, default: 500, env: 'SPREE_LARGE_IMPORT_THRESHOLD' # imports with more rows than this skip per-row UI broadcasts and use bulk processing
|
|
197
197
|
|
|
198
|
+
# maintenance tasks
|
|
199
|
+
# Turn off to run upgrade steps only from the command line: the dashboard
|
|
200
|
+
# page and its Admin API endpoints are refused. For hosts where the people
|
|
201
|
+
# holding a store's admin role are not the ones operating the installation,
|
|
202
|
+
# such as a platform serving many merchants from one database.
|
|
203
|
+
preference :maintenance_tasks_dashboard_enabled, :boolean, default: true, env: 'SPREE_MAINTENANCE_TASKS_DASHBOARD_ENABLED'
|
|
198
204
|
end
|
|
199
205
|
end
|
|
200
206
|
end
|
data/lib/spree/core/engine.rb
CHANGED
|
@@ -46,6 +46,7 @@ module Spree
|
|
|
46
46
|
:admin_agent_tools,
|
|
47
47
|
:store_agent_tools,
|
|
48
48
|
:integrations,
|
|
49
|
+
:maintenance_tasks,
|
|
49
50
|
:number_generators,
|
|
50
51
|
:subscribers,
|
|
51
52
|
:store_authentication_strategies,
|
|
@@ -77,7 +78,8 @@ module Spree
|
|
|
77
78
|
# Core's defaults are concatenated in after_initialize below.
|
|
78
79
|
%i[subscribers actor_classes commission_rules delivery_method_rules seller_requirements
|
|
79
80
|
delivery_rate_providers tax_providers pricing_providers inventory_providers
|
|
80
|
-
payout_providers digital_asset_providers delivery_profile_types pickup_point_providers
|
|
81
|
+
payout_providers digital_asset_providers delivery_profile_types pickup_point_providers
|
|
82
|
+
maintenance_tasks].each do |registry|
|
|
81
83
|
app.config.spree[registry] = []
|
|
82
84
|
end
|
|
83
85
|
app.config.spree.line_item_comparison_hooks = Set.new
|
|
@@ -540,6 +542,13 @@ module Spree
|
|
|
540
542
|
|
|
541
543
|
Rails.application.config.spree.integrations = []
|
|
542
544
|
|
|
545
|
+
# Upgrade steps backed by a task class are registered from the manifests
|
|
546
|
+
# that name them, so adding a step never means editing a second list.
|
|
547
|
+
Rails.application.config.spree.maintenance_tasks.concat [
|
|
548
|
+
'Spree::MaintenanceTasks::UpgradeStep',
|
|
549
|
+
*Spree::Upgrade.steps.filter_map { |step| step['task_class'] }
|
|
550
|
+
]
|
|
551
|
+
|
|
543
552
|
Rails.application.config.spree.validators.addresses = [
|
|
544
553
|
Spree::Addresses::PhoneValidator
|
|
545
554
|
]
|
|
@@ -201,6 +201,13 @@ module Spree
|
|
|
201
201
|
})
|
|
202
202
|
catalog.register_scope(:integrations, group: :settings, resources: -> { [Spree::Integration] })
|
|
203
203
|
|
|
204
|
+
# Sits with the other sensitive access grants rather than under
|
|
205
|
+
# settings: a task can rewrite any data in the install, so out of the
|
|
206
|
+
# box only the admin role holds it (docs/plans/6.0-maintenance-tasks.md).
|
|
207
|
+
catalog.register_scope(:maintenance_tasks, group: :access, resources: -> {
|
|
208
|
+
[Spree::MaintenanceTaskRun, :maintenance_task]
|
|
209
|
+
})
|
|
210
|
+
|
|
204
211
|
# The CORS allowlist is a credential as much as a key is: an origin on
|
|
205
212
|
# it may call the cookie-authenticated refresh endpoint from a browser
|
|
206
213
|
# and read back any signed-in admin's access token. So it sits with
|
data/lib/spree/core/version.rb
CHANGED
data/lib/spree/core.rb
CHANGED
|
@@ -135,7 +135,8 @@ module Spree
|
|
|
135
135
|
stock_reservations: :default,
|
|
136
136
|
tax_identifiers: :default,
|
|
137
137
|
data_requests: :default,
|
|
138
|
-
payouts: :default
|
|
138
|
+
payouts: :default,
|
|
139
|
+
maintenance_tasks: :default
|
|
139
140
|
).tap do |queues|
|
|
140
141
|
# @deprecated The taxons queue was renamed to categories in 6.0; removed in 6.1.
|
|
141
142
|
queues.define_singleton_method(:taxons) do
|
|
@@ -455,6 +456,19 @@ module Spree
|
|
|
455
456
|
custom_fields
|
|
456
457
|
end
|
|
457
458
|
|
|
459
|
+
# Registered maintenance task class names — the tasks an operator can run
|
|
460
|
+
# from the dashboard, the Admin API or the CLI
|
|
461
|
+
# (docs/plans/6.0-maintenance-tasks.md). Extensions append their own from an
|
|
462
|
+
# initializer:
|
|
463
|
+
#
|
|
464
|
+
# Spree.maintenance_tasks << 'SpreeReviews::MaintenanceTasks::RecountRatings'
|
|
465
|
+
#
|
|
466
|
+
# Class names rather than classes, so registration is safe at initializer
|
|
467
|
+
# time and survives code reloading in development.
|
|
468
|
+
#
|
|
469
|
+
# @return [Array<String, Class>]
|
|
470
|
+
singleton_class.delegate :maintenance_tasks, :maintenance_tasks=, to: :spree_config
|
|
471
|
+
|
|
458
472
|
# Registry mapping a numbered resource to the generator that produces its
|
|
459
473
|
# document numbers. With no entry, the store's `document_number_format`
|
|
460
474
|
# preference picks between the sequential and random strategies.
|
|
@@ -644,6 +658,7 @@ require 'spree/core/version'
|
|
|
644
658
|
require 'spree/core/number_generator'
|
|
645
659
|
require 'spree/number_generators/registry'
|
|
646
660
|
require 'spree/migrations'
|
|
661
|
+
require 'spree/upgrade'
|
|
647
662
|
require 'spree/validators'
|
|
648
663
|
require 'spree/core/engine'
|
|
649
664
|
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
# Reads the upgrade manifests shipped inside spree_core — the list of data
|
|
3
|
+
# steps an installation must run after `db:migrate` for a given release
|
|
4
|
+
# boundary (docs/plans/6.0-maintenance-tasks.md).
|
|
5
|
+
#
|
|
6
|
+
# Extracted from lib/tasks/upgrade.rake so the dashboard and the maintenance
|
|
7
|
+
# task runner can read the same manifests the rake walk does; the rake file
|
|
8
|
+
# keeps the walking and printing.
|
|
9
|
+
module Upgrade
|
|
10
|
+
# The first release whose upgrade steps run as maintenance tasks. Nothing
|
|
11
|
+
# recorded the steps of earlier releases, so the dashboard starts here; the
|
|
12
|
+
# command line still walks every manifest.
|
|
13
|
+
FIRST_TRACKED_RELEASE = '6.0'.freeze
|
|
14
|
+
|
|
15
|
+
# Two-segment "5.5" form of the installed Spree version.
|
|
16
|
+
#
|
|
17
|
+
# @return [String]
|
|
18
|
+
def self.installed_minor_version
|
|
19
|
+
Spree.version.split('.').first(2).join('.')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Root directory containing N_M_to_O_P/manifest.yml files inside the
|
|
23
|
+
# spree_core gem.
|
|
24
|
+
#
|
|
25
|
+
# @return [String]
|
|
26
|
+
def self.manifests_root
|
|
27
|
+
File.expand_path('upgrades', __dir__)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# All available manifest directories, parsed into { from:, to:, dir: }.
|
|
31
|
+
# Sorted by `to` ascending, with `from` as a tiebreaker (smallest first)
|
|
32
|
+
# for the rare case where two manifests share a `to` boundary.
|
|
33
|
+
#
|
|
34
|
+
# @return [Array<Hash>]
|
|
35
|
+
def self.available_manifests
|
|
36
|
+
Dir.glob(File.join(manifests_root, '*_to_*')).filter_map do |dir|
|
|
37
|
+
name = File.basename(dir)
|
|
38
|
+
match = name.match(/\A([\d_]+)_to_([\d_]+)\z/)
|
|
39
|
+
next unless match
|
|
40
|
+
|
|
41
|
+
{ from: match[1].tr('_', '.'), to: match[2].tr('_', '.'), dir: dir }
|
|
42
|
+
end.sort_by { |manifest| version_parts(manifest[:to]) + version_parts(manifest[:from]) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Every manifest with its steps loaded, in walk order. Read once per
|
|
46
|
+
# process: the files ship inside the gem and change only with a deploy.
|
|
47
|
+
#
|
|
48
|
+
# @return [Array<Hash>]
|
|
49
|
+
def self.manifests
|
|
50
|
+
@manifests ||= available_manifests.map do |entry|
|
|
51
|
+
YAML.safe_load_file(File.join(entry[:dir], 'manifest.yml')).freeze
|
|
52
|
+
end.freeze
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Every step of every manifest, in walk order, each carrying the manifest
|
|
56
|
+
# boundary it belongs to so callers can group without re-reading the files.
|
|
57
|
+
#
|
|
58
|
+
# @return [Array<Hash>]
|
|
59
|
+
def self.steps
|
|
60
|
+
@steps ||= manifests.flat_map do |manifest|
|
|
61
|
+
manifest['steps'].map do |step|
|
|
62
|
+
step.merge('from' => manifest['from'], 'to' => manifest['to'], 'docs' => manifest['docs'])
|
|
63
|
+
end
|
|
64
|
+
end.freeze
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Whether a step belongs to a release whose runs are recorded, and so has a
|
|
68
|
+
# place in the dashboard's checklist.
|
|
69
|
+
#
|
|
70
|
+
# @param step [Hash] a manifest step
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def self.tracked?(step)
|
|
73
|
+
compare(step['to'], FIRST_TRACKED_RELEASE) >= 0
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The task a step runs: its own class, or the shared runner for a step
|
|
77
|
+
# that names a rake task.
|
|
78
|
+
#
|
|
79
|
+
# @param step [Hash] a manifest step
|
|
80
|
+
# @return [String] a task class name
|
|
81
|
+
def self.task_class_name(step)
|
|
82
|
+
step['task_class'] || 'Spree::MaintenanceTasks::UpgradeStep'
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The arguments that start a step's task: a rake-backed step is named by
|
|
86
|
+
# its id, so the shared runner knows which rake task to invoke.
|
|
87
|
+
#
|
|
88
|
+
# @param step [Hash] a manifest step
|
|
89
|
+
# @return [Hash]
|
|
90
|
+
def self.task_arguments(step)
|
|
91
|
+
step['task_class'] ? {} : { 'step_id' => step['id'] }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# @param step_id [String]
|
|
95
|
+
# @return [Hash, nil]
|
|
96
|
+
def self.find_step(step_id)
|
|
97
|
+
return nil if step_id.blank?
|
|
98
|
+
|
|
99
|
+
steps.find { |step| step['id'] == step_id.to_s }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The boundary this installation has already completed, and therefore the
|
|
103
|
+
# point a pending list starts from.
|
|
104
|
+
#
|
|
105
|
+
# Falls back to the release before the installed one when nothing has been
|
|
106
|
+
# stamped: an installation running 6.0 has, by far most often, come from
|
|
107
|
+
# 5.6, and showing every step of every manifest ever written is worse than
|
|
108
|
+
# showing the one boundary that is almost certainly right. A store that
|
|
109
|
+
# skipped releases runs the earlier steps from the command line, which
|
|
110
|
+
# walks every manifest whatever the boundary.
|
|
111
|
+
#
|
|
112
|
+
# @return [String, nil]
|
|
113
|
+
def self.completed_boundary
|
|
114
|
+
Spree::UpgradeRecord.current_version || previous_boundary
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The `to` of the manifest immediately below the installed version.
|
|
118
|
+
#
|
|
119
|
+
# @return [String, nil]
|
|
120
|
+
def self.previous_boundary
|
|
121
|
+
boundaries = manifests.map { |manifest| manifest['from'] }.uniq
|
|
122
|
+
boundaries.select { |version| compare(version, installed_minor_version) < 0 }.
|
|
123
|
+
max_by { |version| version_parts(version) }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Whether an upgrade applies to this installation at all.
|
|
127
|
+
#
|
|
128
|
+
# False for a store installed fresh at its current version: it has no
|
|
129
|
+
# historical data to convert, so neither the outstanding steps nor the
|
|
130
|
+
# history of past releases describes anything real for it. True again
|
|
131
|
+
# once a later release ships steps it has not run.
|
|
132
|
+
#
|
|
133
|
+
# @return [Boolean]
|
|
134
|
+
def self.relevant?
|
|
135
|
+
!Spree::UpgradeRecord.exists? || Spree::UpgradeRecord.upgraded? || pending_steps.any?
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Whether a step can run here. An optional step belongs to a gem that may
|
|
139
|
+
# not be installed — a payment gateway, for instance — and is skipped
|
|
140
|
+
# where it is absent rather than offered as work that cannot start.
|
|
141
|
+
#
|
|
142
|
+
# @param step [Hash] a manifest step
|
|
143
|
+
# @return [Boolean]
|
|
144
|
+
def self.available?(step)
|
|
145
|
+
return true unless step['optional']
|
|
146
|
+
return Spree::MaintenanceTask.find_registered(step['task_class']).present? if step['task_class']
|
|
147
|
+
|
|
148
|
+
Spree::MaintenanceTask.new.rake_file_defining(step['task']).present?
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Steps this installation still has to run: every manifest above the
|
|
152
|
+
# boundary it has completed, up to and including the installed version.
|
|
153
|
+
#
|
|
154
|
+
# @return [Array<Hash>]
|
|
155
|
+
def self.pending_steps
|
|
156
|
+
boundary = completed_boundary
|
|
157
|
+
|
|
158
|
+
steps.select do |step|
|
|
159
|
+
next false if compare(step['to'], installed_minor_version) > 0
|
|
160
|
+
next false unless available?(step)
|
|
161
|
+
next true if boundary.nil?
|
|
162
|
+
|
|
163
|
+
compare(step['from'], boundary) >= 0
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Steps of manifests below the completed boundary — the ones a fresh or
|
|
168
|
+
# already-upgraded installation does not need. When the boundary was
|
|
169
|
+
# assumed rather than recorded the dashboard says so, and points a store
|
|
170
|
+
# that skipped a release at the command line, which still runs them.
|
|
171
|
+
#
|
|
172
|
+
# @return [Array<Hash>]
|
|
173
|
+
def self.superseded_steps
|
|
174
|
+
boundary = completed_boundary
|
|
175
|
+
return [] if boundary.nil?
|
|
176
|
+
|
|
177
|
+
steps.select { |step| compare(step['from'], boundary) < 0 }
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def self.version_parts(version)
|
|
181
|
+
version.split('.').map { |segment| Integer(segment, 10) rescue 0 }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Compare two dotted-version strings (returns -1, 0, +1).
|
|
185
|
+
def self.compare(first, second)
|
|
186
|
+
version_parts(first) <=> version_parts(second)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
@@ -12,8 +12,15 @@
|
|
|
12
12
|
# live in the upgrade doc at docs/developer/upgrades/5.6-to-6.0.mdx.
|
|
13
13
|
#
|
|
14
14
|
# This manifest is the machine-runnable shape — what `bin/rake spree:upgrade`
|
|
15
|
-
# (in production) and `spree upgrade` (in dev) execute. Every step must be
|
|
16
|
-
#
|
|
15
|
+
# (in production) and `spree upgrade` (in dev) execute. Every step must be
|
|
16
|
+
# idempotent; re-running the full manifest is safe.
|
|
17
|
+
#
|
|
18
|
+
# Steps name a `task_class` — a Spree::MaintenanceTask that the walk runs
|
|
19
|
+
# through the maintenance task runner, so a shell walk and a dashboard run
|
|
20
|
+
# leave the same audit record and the same resume behaviour
|
|
21
|
+
# (docs/plans/6.0-maintenance-tasks.md). Released manifests keep their `task`
|
|
22
|
+
# rake names, which have shipped and may be scripted; the walker runs both
|
|
23
|
+
# kinds.
|
|
17
24
|
---
|
|
18
25
|
from: "5.6"
|
|
19
26
|
to: "6.0"
|
|
@@ -151,7 +158,7 @@ steps:
|
|
|
151
158
|
|
|
152
159
|
- id: migrate_users_to_customers
|
|
153
160
|
name: "Move users onto the gem-owned Customer/AdminUser models"
|
|
154
|
-
|
|
161
|
+
task_class: "Spree::MaintenanceTasks::Upgrade::UsersToCustomers"
|
|
155
162
|
notes: |
|
|
156
163
|
Copies the legacy Devise user table (default spree_users) into
|
|
157
164
|
spree_customers, preserving ids so the renamed customer_id foreign keys
|
|
@@ -261,7 +268,7 @@ steps:
|
|
|
261
268
|
|
|
262
269
|
- id: migrate_deliveries
|
|
263
270
|
name: "Move tracking numbers onto deliveries"
|
|
264
|
-
|
|
271
|
+
task_class: "Spree::MaintenanceTasks::Upgrade::Deliveries"
|
|
265
272
|
notes: |
|
|
266
273
|
Every fulfillment carrying a 5.6 tracking number gets one delivery row
|
|
267
274
|
with that number: the carrier axis (tracking number, carrier, carrier
|
|
@@ -322,7 +329,7 @@ steps:
|
|
|
322
329
|
|
|
323
330
|
- id: migrate_promotion_option_value_rules
|
|
324
331
|
name: "Rewrite Option Value promotion rules to stable option value ids"
|
|
325
|
-
|
|
332
|
+
task_class: "Spree::MaintenanceTasks::Upgrade::PromotionOptionValueRules"
|
|
326
333
|
notes: |
|
|
327
334
|
Spree 6.0 stores Option Value promotion rules against Spree::OptionValue
|
|
328
335
|
ids. Upgrades from 5.6 may still carry join-row ids from
|
|
@@ -365,7 +372,7 @@ steps:
|
|
|
365
372
|
|
|
366
373
|
- id: migrate_returns
|
|
367
374
|
name: "Move returns onto the new returns models"
|
|
368
|
-
|
|
375
|
+
task_class: "Spree::MaintenanceTasks::Upgrade::Returns"
|
|
369
376
|
notes: |
|
|
370
377
|
Converts each ReturnAuthorization into a Spree::Return, or a
|
|
371
378
|
Spree::Exchange when any of its return items named an exchange variant,
|
|
@@ -494,7 +501,7 @@ steps:
|
|
|
494
501
|
|
|
495
502
|
- id: backfill_order_coupon_codes
|
|
496
503
|
name: "Backfill coupon codes on placed orders"
|
|
497
|
-
|
|
504
|
+
task_class: "Spree::MaintenanceTasks::Upgrade::OrderCouponCodes"
|
|
498
505
|
notes: |
|
|
499
506
|
spree_orders gains a coupon_code column in Spree 6, matching carts.
|
|
500
507
|
Historical placed orders applied coupons only through the promotion
|
|
@@ -510,7 +517,7 @@ steps:
|
|
|
510
517
|
|
|
511
518
|
- id: backfill_order_markets
|
|
512
519
|
name: "Backfill order markets"
|
|
513
|
-
|
|
520
|
+
task_class: "Spree::MaintenanceTasks::Upgrade::BackfillOrderMarkets"
|
|
514
521
|
notes: |
|
|
515
522
|
Market is mandatory in Spree 6. Assigns the store's default market to
|
|
516
523
|
orders that predate markets. Channel needs no step here — the 5.4→5.5
|
|
@@ -573,7 +580,7 @@ steps:
|
|
|
573
580
|
|
|
574
581
|
- id: migrate_capture_methods
|
|
575
582
|
name: "Convert payment method auto_capture into capture_method"
|
|
576
|
-
|
|
583
|
+
task_class: "Spree::MaintenanceTasks::Upgrade::CaptureMethods"
|
|
577
584
|
notes: |
|
|
578
585
|
Payment methods say when they charge with a capture_method string
|
|
579
586
|
(checkout, on_dispatch or manual) rather than an auto_capture boolean,
|
|
@@ -625,7 +632,7 @@ steps:
|
|
|
625
632
|
|
|
626
633
|
- id: migrate_stripe_webhook_keys
|
|
627
634
|
name: "Move Stripe webhook signing secrets onto the gateway"
|
|
628
|
-
|
|
635
|
+
task_class: "SpreeStripe::MaintenanceTasks::MigrateWebhookKeys"
|
|
629
636
|
optional: true
|
|
630
637
|
notes: |
|
|
631
638
|
Only runs when spree_stripe is installed; skipped otherwise.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Command-line entry to the same runner the dashboard drives, so a shell run
|
|
4
|
+
# is audited exactly like an operator's (docs/plans/6.0-maintenance-tasks.md).
|
|
5
|
+
#
|
|
6
|
+
# bin/rake "spree:maintenance_tasks:perform[backfill_order_markets]"
|
|
7
|
+
# DRY_RUN=1 bin/rake "spree:maintenance_tasks:perform[capture_methods]"
|
|
8
|
+
# ARGS='batch_size=200' bin/rake "spree:maintenance_tasks:perform[deliveries]"
|
|
9
|
+
namespace :spree do
|
|
10
|
+
namespace :maintenance_tasks do
|
|
11
|
+
desc 'Run a maintenance task synchronously'
|
|
12
|
+
task :perform, [:task_name] => :environment do |_task, args|
|
|
13
|
+
task_name = args[:task_name] || ENV['TASK']
|
|
14
|
+
abort ' Pass a task name: rake "spree:maintenance_tasks:perform[Your::Task]"' if task_name.blank?
|
|
15
|
+
|
|
16
|
+
# Either the class name or the short name the dashboard shows.
|
|
17
|
+
lookup = Spree::MaintenanceTask.find_registered(task_name) ? { task_name: task_name } : { api_type: task_name }
|
|
18
|
+
|
|
19
|
+
result = Spree::MaintenanceTasks::Start.call(
|
|
20
|
+
**lookup,
|
|
21
|
+
arguments: Spree::MaintenanceTasks::ArgumentParser.parse(ENV['ARGS']),
|
|
22
|
+
dry_run: ENV['DRY_RUN'] == '1',
|
|
23
|
+
initiated_via: 'cli',
|
|
24
|
+
inline: true
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
abort " #{result.error}" if result.failure?
|
|
28
|
+
|
|
29
|
+
run = result.value.reload
|
|
30
|
+
puts " #{run.task_name}: #{run.status} — #{run.tick_count} processed#{run.dry_run? ? ' (dry run)' : ''}"
|
|
31
|
+
(run.tallies || {}).each { |key, value| puts " #{key}: #{value}" }
|
|
32
|
+
|
|
33
|
+
if run.errored?
|
|
34
|
+
puts " #{run.error_class}: #{run.error_message}"
|
|
35
|
+
abort ' The run failed. Fix the cause, then re-run this task.'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# A refused duplicate or a cancellation did not do the work either.
|
|
39
|
+
abort " The run did not finish (#{run.status})." unless run.succeeded?
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/tasks/upgrade.rake
CHANGED
|
@@ -33,41 +33,10 @@ namespace :spree do
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
require 'spree/upgrade'
|
|
37
|
+
|
|
36
38
|
module Spree
|
|
37
39
|
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
40
|
# The runner is a class (not a method) so individual concerns (selection,
|
|
72
41
|
# rendering, invocation) stay separable and the plan can be inspected
|
|
73
42
|
# without execution.
|
|
@@ -100,13 +69,7 @@ module Spree
|
|
|
100
69
|
# Plan mode selects exactly what a real run executes, so a dry run after
|
|
101
70
|
# bumping the gem lists every backfill the upgrade is about to perform.
|
|
102
71
|
def eligible_manifests
|
|
103
|
-
Spree::Upgrade.
|
|
104
|
-
.select { |manifest| Spree::Upgrade.compare(manifest[:to], target_version) <= 0 }
|
|
105
|
-
.map { |entry| load_manifest_yaml(entry) }
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
def load_manifest_yaml(entry)
|
|
109
|
-
YAML.safe_load_file(File.join(entry[:dir], 'manifest.yml'))
|
|
72
|
+
Spree::Upgrade.manifests.select { |manifest| Spree::Upgrade.compare(manifest['to'], target_version) <= 0 }
|
|
110
73
|
end
|
|
111
74
|
|
|
112
75
|
def run_full_walk(manifests)
|
|
@@ -122,10 +85,22 @@ module Spree
|
|
|
122
85
|
end
|
|
123
86
|
end
|
|
124
87
|
|
|
88
|
+
stamp_completed_boundaries(manifests) unless dry_run
|
|
89
|
+
|
|
125
90
|
puts
|
|
126
91
|
puts dry_run ? ' (dry run — nothing executed)' : ' Upgrade tasks complete.'
|
|
127
92
|
end
|
|
128
93
|
|
|
94
|
+
# Records the boundaries this walk finished, so a later dashboard visit
|
|
95
|
+
# shows only what is still outstanding rather than every step of every
|
|
96
|
+
# manifest ever written. A partial walk stamps nothing — `invoke` aborts
|
|
97
|
+
# on a failed step, so reaching here means every step succeeded.
|
|
98
|
+
def stamp_completed_boundaries(manifests)
|
|
99
|
+
manifests.each do |manifest|
|
|
100
|
+
Spree::UpgradeRecord.stamp!(manifest['to'], source: 'walk')
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
129
104
|
# STEP=<id> looks across every eligible manifest; we need exactly one
|
|
130
105
|
# match. Multiple matches are likely a manifest bug (two manifests
|
|
131
106
|
# referencing the same step id) but we surface it rather than picking
|
|
@@ -162,38 +137,84 @@ module Spree
|
|
|
162
137
|
puts
|
|
163
138
|
puts " Step #{index}/#{total} [#{step['id']}]"
|
|
164
139
|
puts " #{step['name']}"
|
|
165
|
-
puts " >
|
|
140
|
+
puts " > #{step_command(step)}"
|
|
166
141
|
return unless step['notes']
|
|
167
142
|
|
|
168
143
|
step['notes'].each_line { |line| puts " #{line.chomp}" }
|
|
169
144
|
end
|
|
170
145
|
|
|
171
146
|
def print_step_complete(step)
|
|
172
|
-
puts " ✓ #{step
|
|
147
|
+
puts " ✓ #{step_label(step)} done."
|
|
173
148
|
end
|
|
174
149
|
|
|
150
|
+
def step_label(step)
|
|
151
|
+
step['task_class'] || step['task']
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def step_command(step)
|
|
155
|
+
if step['task_class']
|
|
156
|
+
"bin/rake \"spree:maintenance_tasks:perform[#{step['task_class']}]\""
|
|
157
|
+
else
|
|
158
|
+
"bin/rake #{step['task']}"
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Both step kinds run through the maintenance task runner, so a shell
|
|
163
|
+
# walk records the same audit rows a dashboard run does: a class-backed
|
|
164
|
+
# step runs its own task, a rake-backed step runs inside UpgradeStep.
|
|
165
|
+
#
|
|
166
|
+
# Returns false when the step was skipped, so the caller can leave the
|
|
167
|
+
# "done" line off a step that never ran.
|
|
175
168
|
def invoke(step)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
169
|
+
return false if skip_uninstalled?(step)
|
|
170
|
+
|
|
171
|
+
puts " Running #{step_label(step)}..."
|
|
172
|
+
|
|
173
|
+
result = Spree::MaintenanceTasks::Start.call(
|
|
174
|
+
task_name: Spree::Upgrade.task_class_name(step),
|
|
175
|
+
arguments: step_arguments(step),
|
|
176
|
+
initiated_via: 'cli',
|
|
177
|
+
inline: true
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
abort " #{result.error}" if result.failure?
|
|
181
|
+
|
|
182
|
+
run = result.value.reload
|
|
183
|
+
(run.tallies || {}).each { |key, value| puts " #{key}: #{value}" }
|
|
184
|
+
|
|
185
|
+
if run.errored?
|
|
186
|
+
puts " #{run.error_class}: #{run.error_message}"
|
|
187
|
+
abort ' Step failed. Fix the cause, then re-run this step.'
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Anything short of success — cancelled or still running — means
|
|
191
|
+
# the step's data is not converted, and the steps after it depend on it.
|
|
192
|
+
unless run.succeeded?
|
|
193
|
+
puts " The step did not finish (#{run.status})."
|
|
194
|
+
abort ' Re-run this step once it can finish.'
|
|
185
195
|
end
|
|
186
196
|
|
|
187
|
-
|
|
197
|
+
true
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# A rake-backed step is named by id; a class-backed step that reads
|
|
201
|
+
# environment variables gets the ones the operator exported, so
|
|
202
|
+
# `CONFIRM_NO_PEPPER=true bin/rake spree:upgrade` reaches it.
|
|
203
|
+
def step_arguments(step)
|
|
204
|
+
return Spree::Upgrade.task_arguments(step) unless step['task_class']
|
|
205
|
+
|
|
206
|
+
task_class = Spree::MaintenanceTask.find_registered(step['task_class'])
|
|
207
|
+
task_class.respond_to?(:arguments_from_environment) ? task_class.arguments_from_environment : {}
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
# Steps contributed by optional gems (payment gateways, for instance)
|
|
211
|
+
# are absent unless that gem is installed. Skipping keeps the upgrade
|
|
212
|
+
# runnable on installs that never had the gem, while an install that
|
|
213
|
+
# does have it still gets the backfill.
|
|
214
|
+
def skip_uninstalled?(step)
|
|
215
|
+
return false if Spree::Upgrade.available?(step)
|
|
188
216
|
|
|
189
|
-
|
|
190
|
-
# single `rake spree:upgrade` run re-invoke aggregators that share
|
|
191
|
-
# subtasks across multiple manifests (e.g. two manifests both
|
|
192
|
-
# depending on `spree:install:migrations` would otherwise only
|
|
193
|
-
# invoke it once).
|
|
194
|
-
rake_task = Rake::Task[task]
|
|
195
|
-
rake_task.reenable
|
|
196
|
-
rake_task.invoke
|
|
217
|
+
puts " Skipping #{step_label(step)} — not installed."
|
|
197
218
|
true
|
|
198
219
|
end
|
|
199
220
|
end
|