strata-cli 0.1.0.beta
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 +7 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +65 -0
- data/LICENSE +21 -0
- data/README.md +465 -0
- data/Rakefile +10 -0
- data/exe/strata +6 -0
- data/lib/strata/cli/ai/client.rb +63 -0
- data/lib/strata/cli/ai/configuration.rb +48 -0
- data/lib/strata/cli/ai/services/table_generator.rb +282 -0
- data/lib/strata/cli/api/client.rb +170 -0
- data/lib/strata/cli/api/connection_error_handler.rb +54 -0
- data/lib/strata/cli/configuration.rb +135 -0
- data/lib/strata/cli/credentials.rb +83 -0
- data/lib/strata/cli/descriptions/create/migration.txt +25 -0
- data/lib/strata/cli/descriptions/create/relation.txt +14 -0
- data/lib/strata/cli/descriptions/create/table.txt +23 -0
- data/lib/strata/cli/descriptions/datasource/add.txt +15 -0
- data/lib/strata/cli/descriptions/datasource/auth.txt +14 -0
- data/lib/strata/cli/descriptions/datasource/exec.txt +7 -0
- data/lib/strata/cli/descriptions/datasource/meta.txt +11 -0
- data/lib/strata/cli/descriptions/datasource/tables.txt +12 -0
- data/lib/strata/cli/descriptions/datasource/test.txt +8 -0
- data/lib/strata/cli/descriptions/deploy/deploy.txt +24 -0
- data/lib/strata/cli/descriptions/deploy/status.txt +9 -0
- data/lib/strata/cli/descriptions/init.txt +14 -0
- data/lib/strata/cli/generators/datasource.rb +83 -0
- data/lib/strata/cli/generators/group.rb +13 -0
- data/lib/strata/cli/generators/migration.rb +71 -0
- data/lib/strata/cli/generators/project.rb +190 -0
- data/lib/strata/cli/generators/relation.rb +64 -0
- data/lib/strata/cli/generators/table.rb +143 -0
- data/lib/strata/cli/generators/templates/adapters/athena.yml +53 -0
- data/lib/strata/cli/generators/templates/adapters/druid.yml +42 -0
- data/lib/strata/cli/generators/templates/adapters/duckdb.yml +36 -0
- data/lib/strata/cli/generators/templates/adapters/mysql.yml +45 -0
- data/lib/strata/cli/generators/templates/adapters/postgres.yml +48 -0
- data/lib/strata/cli/generators/templates/adapters/snowflake.yml +69 -0
- data/lib/strata/cli/generators/templates/adapters/sqlserver.yml +45 -0
- data/lib/strata/cli/generators/templates/adapters/trino.yml +56 -0
- data/lib/strata/cli/generators/templates/datasources.yml +4 -0
- data/lib/strata/cli/generators/templates/migration.rename.yml +15 -0
- data/lib/strata/cli/generators/templates/migration.swap.yml +13 -0
- data/lib/strata/cli/generators/templates/project.yml +36 -0
- data/lib/strata/cli/generators/templates/rel.domain.yml +43 -0
- data/lib/strata/cli/generators/templates/strata.yml +24 -0
- data/lib/strata/cli/generators/templates/table.table_name.yml +118 -0
- data/lib/strata/cli/generators/templates/test.yml +34 -0
- data/lib/strata/cli/generators/test.rb +48 -0
- data/lib/strata/cli/guard.rb +21 -0
- data/lib/strata/cli/helpers/color_helper.rb +103 -0
- data/lib/strata/cli/helpers/command_context.rb +41 -0
- data/lib/strata/cli/helpers/datasource_helper.rb +62 -0
- data/lib/strata/cli/helpers/description_helper.rb +18 -0
- data/lib/strata/cli/helpers/project_helper.rb +85 -0
- data/lib/strata/cli/helpers/prompts.rb +42 -0
- data/lib/strata/cli/helpers/table_filter.rb +48 -0
- data/lib/strata/cli/main.rb +71 -0
- data/lib/strata/cli/sub_commands/audit.rb +262 -0
- data/lib/strata/cli/sub_commands/create.rb +419 -0
- data/lib/strata/cli/sub_commands/datasource.rb +353 -0
- data/lib/strata/cli/sub_commands/deploy.rb +433 -0
- data/lib/strata/cli/sub_commands/project.rb +38 -0
- data/lib/strata/cli/sub_commands/table.rb +58 -0
- data/lib/strata/cli/terminal.rb +102 -0
- data/lib/strata/cli/ui/autocomplete.rb +93 -0
- data/lib/strata/cli/ui/field_editor.rb +215 -0
- data/lib/strata/cli/utils/archive.rb +137 -0
- data/lib/strata/cli/utils/deployment_monitor.rb +445 -0
- data/lib/strata/cli/utils/git.rb +253 -0
- data/lib/strata/cli/utils/import_manager.rb +190 -0
- data/lib/strata/cli/utils/test_reporter.rb +131 -0
- data/lib/strata/cli/utils/yaml_import_resolver.rb +91 -0
- data/lib/strata/cli/utils.rb +39 -0
- data/lib/strata/cli/version.rb +7 -0
- data/lib/strata/cli.rb +36 -0
- data/sig/strata/cli.rbs +6 -0
- metadata +306 -0
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../guard"
|
|
4
|
+
require_relative "../terminal"
|
|
5
|
+
require_relative "../ui/autocomplete"
|
|
6
|
+
require_relative "../ui/field_editor"
|
|
7
|
+
require_relative "../credentials"
|
|
8
|
+
require_relative "../helpers/table_filter"
|
|
9
|
+
require_relative "../ai/services/table_generator"
|
|
10
|
+
require_relative "../helpers/color_helper"
|
|
11
|
+
require_relative "../helpers/prompts"
|
|
12
|
+
require_relative "../helpers/description_helper"
|
|
13
|
+
require_relative "../helpers/command_context"
|
|
14
|
+
require "tty-prompt"
|
|
15
|
+
require "tty-spinner"
|
|
16
|
+
require "yaml"
|
|
17
|
+
require "dwh"
|
|
18
|
+
require "fileutils"
|
|
19
|
+
|
|
20
|
+
module Strata
|
|
21
|
+
module CLI
|
|
22
|
+
module SubCommands
|
|
23
|
+
class Create < Thor
|
|
24
|
+
include Guard
|
|
25
|
+
include Terminal
|
|
26
|
+
include Prompts
|
|
27
|
+
include Thor::Actions
|
|
28
|
+
include Helpers::CommandContext
|
|
29
|
+
extend Helpers::DescriptionHelper
|
|
30
|
+
|
|
31
|
+
desc "table TABLE_PATH", "Create a semantic table model"
|
|
32
|
+
long_desc_from_file("create/table")
|
|
33
|
+
|
|
34
|
+
method_option :datasource, aliases: "-d", type: :string, required: false,
|
|
35
|
+
desc: "Datasource key from datasources.yml"
|
|
36
|
+
|
|
37
|
+
def table(table_path = nil)
|
|
38
|
+
return unless datasource_key
|
|
39
|
+
|
|
40
|
+
# If table_path provided, skip search for speed
|
|
41
|
+
if table_path
|
|
42
|
+
handle_table_creation_with_path(table_path)
|
|
43
|
+
else
|
|
44
|
+
handle_table_creation_interactive
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc "relation RELATION_PATH", "Create a relation (join) definition file"
|
|
49
|
+
long_desc_from_file("create/relation")
|
|
50
|
+
|
|
51
|
+
method_option :datasource, aliases: "-d", type: :string, required: false,
|
|
52
|
+
desc: "Datasource key from datasources.yml"
|
|
53
|
+
|
|
54
|
+
def relation(relation_path)
|
|
55
|
+
return unless datasource_key
|
|
56
|
+
|
|
57
|
+
create_relation_file(relation_path)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
desc "migration SUBCOMMAND(rename or swap)", "Create migration files (rename or swap)"
|
|
61
|
+
long_desc_from_file "create/migration"
|
|
62
|
+
method_option :entity, aliases: "-e", type: :string, required: false,
|
|
63
|
+
desc: "Entity type: dimension, measure, table, or datasource"
|
|
64
|
+
method_option :from, aliases: "-f", type: :string, required: false,
|
|
65
|
+
desc: "Current/source entity name"
|
|
66
|
+
method_option :to, aliases: "-t", type: :string, required: false,
|
|
67
|
+
desc: "New/target entity name"
|
|
68
|
+
|
|
69
|
+
def migration(subcommand)
|
|
70
|
+
case subcommand
|
|
71
|
+
when "rename"
|
|
72
|
+
migration_rename
|
|
73
|
+
when "swap"
|
|
74
|
+
migration_swap
|
|
75
|
+
else
|
|
76
|
+
raise Strata::CommandError, "Unknown migration subcommand: #{subcommand}. Use 'rename' or 'swap'."
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
desc "migration rename", "Create a migration file to rename an entity"
|
|
81
|
+
method_option :entity, aliases: "-e", type: :string, required: true,
|
|
82
|
+
desc: "Entity type: dimension, measure, table, or datasource"
|
|
83
|
+
method_option :from, aliases: "-f", type: :string, required: true,
|
|
84
|
+
desc: "Current entity name"
|
|
85
|
+
method_option :to, aliases: "-t", type: :string, required: true,
|
|
86
|
+
desc: "New entity name"
|
|
87
|
+
|
|
88
|
+
def migration_rename
|
|
89
|
+
validate_entity_type(options[:entity], "rename")
|
|
90
|
+
validate_required_params(options[:entity], options[:from], options[:to])
|
|
91
|
+
|
|
92
|
+
hook = prompt_migration_hook("rename")
|
|
93
|
+
create_rename_migration(options[:entity], options[:from], options[:to], hook)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
desc "migration swap", "Create a migration file to swap entity references"
|
|
97
|
+
method_option :entity, aliases: "-e", type: :string, required: true,
|
|
98
|
+
desc: "Entity type: dimension, measure, or table"
|
|
99
|
+
method_option :from, aliases: "-f", type: :string, required: true,
|
|
100
|
+
desc: "Source entity name"
|
|
101
|
+
method_option :to, aliases: "-t", type: :string, required: true,
|
|
102
|
+
desc: "Target entity name"
|
|
103
|
+
|
|
104
|
+
def migration_swap
|
|
105
|
+
validate_entity_type(options[:entity], "swap")
|
|
106
|
+
validate_required_params(options[:entity], options[:from], options[:to])
|
|
107
|
+
|
|
108
|
+
hook = prompt_migration_hook("swap")
|
|
109
|
+
create_swap_migration(options[:entity], options[:from], options[:to], hook)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
def handle_table_creation_with_path(table_path)
|
|
115
|
+
# Parse the path to extract schema, physical name, and model path
|
|
116
|
+
parsed = parse_table_path(table_path)
|
|
117
|
+
|
|
118
|
+
# Try to find the table in the database
|
|
119
|
+
table_found = find_table_in_database(parsed[:physical_name], parsed[:schema])
|
|
120
|
+
|
|
121
|
+
if table_found
|
|
122
|
+
proceed_with_table_creation(table_found, parsed)
|
|
123
|
+
else
|
|
124
|
+
handle_table_not_found(parsed)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def handle_table_creation_interactive
|
|
129
|
+
return if all_tables.empty?
|
|
130
|
+
|
|
131
|
+
selected_table = resolve_table_selection(nil)
|
|
132
|
+
return unless selected_table
|
|
133
|
+
|
|
134
|
+
# For interactive, derive path from table name
|
|
135
|
+
default_path = TableFilter.display_name(selected_table).downcase.gsub(/\s+/, "_")
|
|
136
|
+
table_path = prompt.ask(" Table path (e.g., games/event_details):", default: default_path)
|
|
137
|
+
return unless table_path
|
|
138
|
+
|
|
139
|
+
parsed = parse_table_path(table_path)
|
|
140
|
+
proceed_with_table_creation(selected_table, parsed)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def parse_table_path(path)
|
|
144
|
+
# Examples:
|
|
145
|
+
# "call_center" -> { model_path: "call_center", physical_name: "call_center", schema: nil }
|
|
146
|
+
# "games/event_details" -> { model_path: "games/event_details", physical_name: "event_details", schema: nil }
|
|
147
|
+
# "contact/dse.call_center_d" -> { model_path: "contact/dse.call_center_d", physical_name: "call_center_d", schema: "dse" }
|
|
148
|
+
# "contact/help_center/visits" -> { model_path: "contact/help_center/visits", physical_name: "visits", schema: nil }
|
|
149
|
+
|
|
150
|
+
path_parts = path.split("/")
|
|
151
|
+
last_part = path_parts.last
|
|
152
|
+
|
|
153
|
+
# Check if last part contains schema (e.g., "dse.call_center_d")
|
|
154
|
+
schema = nil
|
|
155
|
+
physical_name = last_part
|
|
156
|
+
|
|
157
|
+
if last_part.include?(".")
|
|
158
|
+
parts = last_part.split(".", 2)
|
|
159
|
+
schema = parts[0]
|
|
160
|
+
physical_name = parts[1]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
{
|
|
164
|
+
model_path: path,
|
|
165
|
+
physical_name: physical_name.downcase,
|
|
166
|
+
schema: schema&.downcase
|
|
167
|
+
}
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def find_table_in_database(physical_name, schema)
|
|
171
|
+
# Try to fetch metadata directly (skip table list for speed when path provided)
|
|
172
|
+
qualifiers = schema ? {schema: schema} : {}
|
|
173
|
+
qualified_name = schema ? "#{schema}.#{physical_name}" : physical_name
|
|
174
|
+
|
|
175
|
+
begin
|
|
176
|
+
with_spinner("Checking table existence...") do
|
|
177
|
+
table_metadata = adapter.metadata(physical_name, **qualifiers)
|
|
178
|
+
# If metadata returns a table with no columns, the table doesn't exist
|
|
179
|
+
# (some adapters return empty table instead of raising error)
|
|
180
|
+
raise StandardError, "Table not found" if table_metadata.columns.empty?
|
|
181
|
+
|
|
182
|
+
qualified_name
|
|
183
|
+
end
|
|
184
|
+
rescue
|
|
185
|
+
nil
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def handle_table_not_found(parsed)
|
|
190
|
+
table_name = parsed[:physical_name]
|
|
191
|
+
schema_info = parsed[:schema] ? " in schema '#{parsed[:schema]}'" : ""
|
|
192
|
+
say "\nCould not find table '#{table_name}'#{schema_info} in datasource '#{datasource_key}'.", :red
|
|
193
|
+
|
|
194
|
+
if prompt.yes?("Proceed anyway or cancel?")
|
|
195
|
+
proceed_with_table_creation(nil, parsed)
|
|
196
|
+
else
|
|
197
|
+
say "Cancelled.", :yellow
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def proceed_with_table_creation(selected_table, parsed)
|
|
202
|
+
# If table not found, use parsed physical_name
|
|
203
|
+
physical_table_name = selected_table || (parsed[:schema] ? "#{parsed[:schema]}.#{parsed[:physical_name]}" : parsed[:physical_name])
|
|
204
|
+
|
|
205
|
+
model_config = prompt_model_config(parsed)
|
|
206
|
+
return unless model_config[:name]
|
|
207
|
+
|
|
208
|
+
# Only fetch columns if we have a valid table
|
|
209
|
+
columns = []
|
|
210
|
+
if selected_table
|
|
211
|
+
columns = fetch_columns(selected_table)
|
|
212
|
+
say " Found #{columns.length} columns\n", :cyan
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
fields = if columns.any?
|
|
216
|
+
generate_fields(physical_table_name, columns, model_config)
|
|
217
|
+
else
|
|
218
|
+
[]
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Interactive field editor
|
|
222
|
+
result = run_field_editor(fields, physical_table_name, columns, model_config)
|
|
223
|
+
return if result.nil?
|
|
224
|
+
|
|
225
|
+
confirmed_fields = result[:fields]
|
|
226
|
+
final_model_context = result[:model_context]
|
|
227
|
+
|
|
228
|
+
if confirmed_fields.empty?
|
|
229
|
+
say MSG_NO_FIELDS_CONFIRMED, :yellow
|
|
230
|
+
return unless prompt.yes?(MSG_CONTINUE_NO_FIELDS)
|
|
231
|
+
else
|
|
232
|
+
say MSG_FIELDS_CONFIRMED % confirmed_fields.length, :cyan
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
save_model_file(parsed, physical_table_name, confirmed_fields, final_model_context)
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
def resolve_table_selection(initial_filter)
|
|
239
|
+
autocomplete = Autocomplete.new(prompt: prompt)
|
|
240
|
+
autocomplete.select(
|
|
241
|
+
MSG_SEARCH_TABLE,
|
|
242
|
+
all_tables,
|
|
243
|
+
display_transform: ->(table) { TableFilter.display_name(table) },
|
|
244
|
+
default_filter: initial_filter
|
|
245
|
+
)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def prompt_model_config(parsed)
|
|
249
|
+
# Default name should be the table name (without schema), not a display name
|
|
250
|
+
# This matches the requirement: "The name and physical name will be event_details"
|
|
251
|
+
default_table_name = parsed[:physical_name]
|
|
252
|
+
|
|
253
|
+
{
|
|
254
|
+
name: prompt.ask(MSG_MODEL_DISPLAY_NAME, default: default_table_name),
|
|
255
|
+
description: prompt.ask(MSG_MODEL_DESCRIPTION, default: "")
|
|
256
|
+
}
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def fetch_columns(table_name)
|
|
260
|
+
with_spinner("Fetching column metadata...") do
|
|
261
|
+
# Parse qualified table name if present (e.g., "schema.table" -> table name and schema)
|
|
262
|
+
qualifiers = {}
|
|
263
|
+
|
|
264
|
+
if table_name.include?(".")
|
|
265
|
+
parts = table_name.split(".", 2)
|
|
266
|
+
qualifiers[:schema] = parts.first
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
metadata = adapter.metadata(table_name, **qualifiers)
|
|
270
|
+
metadata.columns.map { |c| {name: c.name, type: c.data_type} }
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def generate_fields(table, columns, context)
|
|
275
|
+
generator = AI::Services::TableGenerator.new
|
|
276
|
+
|
|
277
|
+
if generator.ai_available?
|
|
278
|
+
with_spinner("Analyzing table columns...") do
|
|
279
|
+
generator.call(
|
|
280
|
+
table_name: table,
|
|
281
|
+
columns: columns,
|
|
282
|
+
datasource: datasource_key,
|
|
283
|
+
user_context: context
|
|
284
|
+
)
|
|
285
|
+
end
|
|
286
|
+
else
|
|
287
|
+
with_spinner("Generating basic field definitions...") do
|
|
288
|
+
generator.call(
|
|
289
|
+
table_name: table,
|
|
290
|
+
columns: columns,
|
|
291
|
+
datasource: datasource_key
|
|
292
|
+
)
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def run_field_editor(fields, table, columns, context)
|
|
298
|
+
editor = FieldEditor.new(
|
|
299
|
+
fields,
|
|
300
|
+
prompt: prompt,
|
|
301
|
+
table_context: {
|
|
302
|
+
table_name: table,
|
|
303
|
+
columns: columns,
|
|
304
|
+
datasource: datasource_key
|
|
305
|
+
},
|
|
306
|
+
model_context: context
|
|
307
|
+
)
|
|
308
|
+
result = editor.run
|
|
309
|
+
|
|
310
|
+
if result.nil?
|
|
311
|
+
say "\nCancelled.", :yellow
|
|
312
|
+
nil
|
|
313
|
+
else
|
|
314
|
+
result
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def save_model_file(parsed, physical_table_name, fields, context)
|
|
319
|
+
# Build output path based on parsed model_path
|
|
320
|
+
# e.g., "games/event_details" -> "models/games/tbl.event_details.yml"
|
|
321
|
+
# e.g., "contact/dse.call_center_d" -> "models/contact/tbl.dse.call_center_d.yml"
|
|
322
|
+
# Filenames are standardized to lowercase
|
|
323
|
+
|
|
324
|
+
path_parts = parsed[:model_path].split("/")
|
|
325
|
+
last_part = path_parts.last.downcase
|
|
326
|
+
|
|
327
|
+
if path_parts.length > 1
|
|
328
|
+
subdir = path_parts[0..-2].join("/")
|
|
329
|
+
output_path = "models/#{subdir}/tbl.#{last_part}.yml"
|
|
330
|
+
else
|
|
331
|
+
output_path = "models/tbl.#{last_part}.yml"
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
# Extract physical name (without schema for the name field, but keep full for physical_name)
|
|
335
|
+
parsed[:physical_name]
|
|
336
|
+
parsed[:physical_name] # Use just the table name, not schema
|
|
337
|
+
|
|
338
|
+
options_with_name = options.merge(name: context[:name])
|
|
339
|
+
|
|
340
|
+
require_relative "../generators/table"
|
|
341
|
+
gen = Generators::Table.new(
|
|
342
|
+
[parsed[:model_path], datasource_key, physical_table_name, fields, context],
|
|
343
|
+
options_with_name
|
|
344
|
+
)
|
|
345
|
+
gen.invoke_all
|
|
346
|
+
|
|
347
|
+
say MSG_CREATED_MODEL % output_path, :green
|
|
348
|
+
say MSG_EDIT_MODEL_HINT, :yellow
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def create_relation_file(relation_path)
|
|
352
|
+
require_relative "../generators/relation"
|
|
353
|
+
|
|
354
|
+
relation_generator = Generators::Relation.new([relation_path, datasource_key])
|
|
355
|
+
relation_generator.invoke_all
|
|
356
|
+
|
|
357
|
+
say "\n💡 Edit the file to define your joins between tables", :yellow
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def validate_entity_type(entity, operation)
|
|
361
|
+
valid_entities = case operation
|
|
362
|
+
when "rename"
|
|
363
|
+
%w[dimension measure table datasource]
|
|
364
|
+
when "swap"
|
|
365
|
+
%w[dimension measure table]
|
|
366
|
+
else
|
|
367
|
+
[]
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
unless valid_entities.include?(entity&.downcase)
|
|
371
|
+
valid_list = valid_entities.join(", ")
|
|
372
|
+
if operation == "swap" && entity&.downcase == "datasource"
|
|
373
|
+
raise Strata::CommandError, "Swap operation does not support 'datasource' entity type. Valid types: #{valid_list}"
|
|
374
|
+
else
|
|
375
|
+
raise Strata::CommandError, "Invalid entity type: #{entity}. Valid types for #{operation}: #{valid_list}"
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def validate_required_params(entity, from, to)
|
|
381
|
+
raise Strata::CommandError, "Missing required option: --entity" if entity.nil? || entity.strip.empty?
|
|
382
|
+
raise Strata::CommandError, "Missing required option: --from" if from.nil? || from.strip.empty?
|
|
383
|
+
raise Strata::CommandError, "Missing required option: --to" if to.nil? || to.strip.empty?
|
|
384
|
+
|
|
385
|
+
if from.strip == to.strip
|
|
386
|
+
say "Warning: --from and --to have the same value. Migration will have no effect.", ColorHelper.warning
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def prompt_migration_hook(operation)
|
|
391
|
+
hook_options = Prompts::MIGRATION_HOOK_OPTIONS
|
|
392
|
+
default_key = Prompts.default_migration_hook(operation)
|
|
393
|
+
|
|
394
|
+
prompt.select("Migration hook:", hook_options, default: default_key)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def create_rename_migration(entity, from, to, hook = "pre")
|
|
398
|
+
require_relative "../generators/migration"
|
|
399
|
+
|
|
400
|
+
migration_generator = Generators::Migration.new(
|
|
401
|
+
["rename", entity, from, to],
|
|
402
|
+
{hook: hook}
|
|
403
|
+
)
|
|
404
|
+
migration_generator.invoke_all
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def create_swap_migration(entity, from, to, hook = "post")
|
|
408
|
+
require_relative "../generators/migration"
|
|
409
|
+
|
|
410
|
+
migration_generator = Generators::Migration.new(
|
|
411
|
+
["swap", entity, from, to],
|
|
412
|
+
{hook: hook}
|
|
413
|
+
)
|
|
414
|
+
migration_generator.invoke_all
|
|
415
|
+
end
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
end
|
|
419
|
+
end
|