strata-cli 0.1.3.beta → 0.1.5.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/CHANGELOG.md +11 -0
  4. data/README.md +3 -2
  5. data/lib/strata/cli/ai/services/table_generator.rb +8 -4
  6. data/lib/strata/cli/api/client.rb +4 -4
  7. data/lib/strata/cli/api/connection_error_handler.rb +1 -1
  8. data/lib/strata/cli/configuration.rb +4 -3
  9. data/lib/strata/cli/credentials.rb +2 -0
  10. data/lib/strata/cli/descriptions/create/migration.txt +10 -0
  11. data/lib/strata/cli/descriptions/create/relation.txt +6 -0
  12. data/lib/strata/cli/descriptions/create/table.txt +13 -0
  13. data/lib/strata/cli/descriptions/datasource/add.txt +6 -0
  14. data/lib/strata/cli/descriptions/datasource/meta.txt +5 -0
  15. data/lib/strata/cli/descriptions/datasource/tables.txt +6 -0
  16. data/lib/strata/cli/descriptions/datasource/test.txt +5 -0
  17. data/lib/strata/cli/descriptions/deploy/deploy.txt +13 -0
  18. data/lib/strata/cli/descriptions/deploy/status.txt +3 -0
  19. data/lib/strata/cli/descriptions/init.txt +6 -0
  20. data/lib/strata/cli/error_reporter.rb +70 -0
  21. data/lib/strata/cli/generators/datasource.rb +4 -1
  22. data/lib/strata/cli/generators/group.rb +14 -8
  23. data/lib/strata/cli/generators/migration.rb +1 -1
  24. data/lib/strata/cli/generators/project.rb +146 -130
  25. data/lib/strata/cli/generators/table.rb +3 -0
  26. data/lib/strata/cli/generators/templates/table.table_name.yml +6 -0
  27. data/lib/strata/cli/guard.rb +2 -0
  28. data/lib/strata/cli/helpers/command_context.rb +23 -4
  29. data/lib/strata/cli/helpers/datasource_helper.rb +34 -1
  30. data/lib/strata/cli/helpers/description_helper.rb +2 -0
  31. data/lib/strata/cli/helpers/project_helper.rb +28 -0
  32. data/lib/strata/cli/helpers/prompts.rb +2 -0
  33. data/lib/strata/cli/main.rb +5 -1
  34. data/lib/strata/cli/sub_commands/audit.rb +19 -5
  35. data/lib/strata/cli/sub_commands/create.rb +41 -19
  36. data/lib/strata/cli/sub_commands/datasource.rb +11 -2
  37. data/lib/strata/cli/sub_commands/deploy.rb +79 -36
  38. data/lib/strata/cli/sub_commands/project.rb +4 -6
  39. data/lib/strata/cli/sub_commands/table.rb +1 -1
  40. data/lib/strata/cli/terminal.rb +2 -0
  41. data/lib/strata/cli/ui/autocomplete.rb +2 -0
  42. data/lib/strata/cli/ui/field_editor.rb +6 -0
  43. data/lib/strata/cli/utils/archive.rb +1 -3
  44. data/lib/strata/cli/utils/deployment_monitor.rb +27 -25
  45. data/lib/strata/cli/utils/git.rb +10 -7
  46. data/lib/strata/cli/utils/import_manager.rb +21 -23
  47. data/lib/strata/cli/utils/test_reporter.rb +16 -25
  48. data/lib/strata/cli/utils/version_checker.rb +4 -6
  49. data/lib/strata/cli/utils/yaml_import_resolver.rb +5 -2
  50. data/lib/strata/cli/utils.rb +2 -0
  51. data/lib/strata/cli/version.rb +1 -1
  52. data/lib/strata/cli.rb +8 -0
  53. metadata +30 -28
@@ -126,9 +126,15 @@ module Strata
126
126
  end
127
127
 
128
128
  def handle_table_creation_interactive
129
- return if all_tables.empty?
129
+ fetch_result = table_fetch_result
130
+ tables = fetch_result[:tables]
131
+ if tables.empty?
132
+ return handle_table_creation_without_connection if fetch_result[:failed]
130
133
 
131
- selected_table = resolve_table_selection(nil)
134
+ return
135
+ end
136
+
137
+ selected_table = resolve_table_selection(tables, nil)
132
138
  return unless selected_table
133
139
 
134
140
  # For interactive, derive path from table name
@@ -191,21 +197,26 @@ module Strata
191
197
  schema_info = parsed[:schema] ? " in schema '#{parsed[:schema]}'" : ""
192
198
  say "\nCould not find table '#{table_name}'#{schema_info} in datasource '#{datasource_key}'.", :red
193
199
 
194
- if prompt.yes?("Proceed anyway or cancel?")
195
- proceed_with_table_creation(nil, parsed)
200
+ if prompt.yes?("Proceed with template-only file (no generated fields)?", default: true)
201
+ proceed_with_table_creation(nil, parsed, skip_field_editor: true)
196
202
  else
197
203
  say "Cancelled.", :yellow
198
204
  end
199
205
  end
200
206
 
201
- def proceed_with_table_creation(selected_table, parsed)
207
+ def proceed_with_table_creation(selected_table, parsed, skip_field_editor: false)
202
208
  # If table not found, use parsed physical_name
203
209
  physical_table_name = selected_table || (parsed[:schema] ? "#{parsed[:schema]}.#{parsed[:physical_name]}" : parsed[:physical_name])
204
210
 
205
211
  model_config = prompt_model_config(parsed)
206
212
  return unless model_config[:name]
207
213
 
208
- # Only fetch columns if we have a valid table
214
+ if skip_field_editor || selected_table.nil?
215
+ say "Skipping field generation. Template file will be created without fields.", :yellow
216
+ save_model_file(parsed, physical_table_name, [], model_config)
217
+ return
218
+ end
219
+
209
220
  columns = []
210
221
  if selected_table
211
222
  columns = fetch_columns(selected_table)
@@ -218,7 +229,6 @@ module Strata
218
229
  []
219
230
  end
220
231
 
221
- # Interactive field editor
222
232
  result = run_field_editor(fields, physical_table_name, columns, model_config)
223
233
  return if result.nil?
224
234
 
@@ -235,16 +245,27 @@ module Strata
235
245
  save_model_file(parsed, physical_table_name, confirmed_fields, final_model_context)
236
246
  end
237
247
 
238
- def resolve_table_selection(initial_filter)
248
+ def resolve_table_selection(tables, initial_filter)
239
249
  autocomplete = Autocomplete.new(prompt: prompt)
240
250
  autocomplete.select(
241
251
  MSG_SEARCH_TABLE,
242
- all_tables,
252
+ tables,
243
253
  display_transform: ->(table) { TableFilter.display_name(table) },
244
254
  default_filter: initial_filter
245
255
  )
246
256
  end
247
257
 
258
+ def handle_table_creation_without_connection
259
+ say "\nConnection failed. You can still create a template-only table model.", :yellow
260
+ return unless prompt.yes?("Continue without fetching table metadata?", default: true)
261
+
262
+ table_path = prompt.ask(" Table path (e.g., games/event_details):")
263
+ return unless table_path
264
+
265
+ parsed = parse_table_path(table_path)
266
+ proceed_with_table_creation(nil, parsed, skip_field_editor: true)
267
+ end
268
+
248
269
  def prompt_model_config(parsed)
249
270
  # Default name should be the table name (without schema), not a display name
250
271
  # This matches the requirement: "The name and physical name will be event_details"
@@ -367,14 +388,15 @@ module Strata
367
388
  []
368
389
  end
369
390
 
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
391
+ return if valid_entities.include?(entity&.downcase)
392
+
393
+ valid_list = valid_entities.join(", ")
394
+ unless operation == "swap" && entity&.downcase == "datasource"
395
+ raise Strata::CommandError, "Invalid entity type: #{entity}. Valid types for #{operation}: #{valid_list}"
377
396
  end
397
+
398
+ raise Strata::CommandError,
399
+ "Swap operation does not support 'datasource' entity type. Valid types: #{valid_list}"
378
400
  end
379
401
 
380
402
  def validate_required_params(entity, from, to)
@@ -382,9 +404,9 @@ module Strata
382
404
  raise Strata::CommandError, "Missing required option: --from" if from.nil? || from.strip.empty?
383
405
  raise Strata::CommandError, "Missing required option: --to" if to.nil? || to.strip.empty?
384
406
 
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
407
+ return unless from.strip == to.strip
408
+
409
+ say "Warning: --from and --to have the same value. Migration will have no effect.", ColorHelper.warning
388
410
  end
389
411
 
390
412
  def prompt_migration_hook(operation)
@@ -1,6 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative "../guard"
2
4
  require_relative "../credentials"
3
5
  require_relative "../terminal"
6
+ require_relative "../utils/git"
4
7
  require "tty-prompt"
5
8
  require_relative "../helpers/datasource_helper"
6
9
  require_relative "../helpers/description_helper"
@@ -18,7 +21,7 @@ module Strata
18
21
  desc "adapters", "Lists supported data warehouse adapters"
19
22
  def adapters
20
23
  say "\n\tSupported Adapters\n\n", :yellow
21
- DWH.adapters.keys.each do
24
+ DWH.adapters.each_key do
22
25
  say "\t\t● #{it}", :magenta
23
26
  end
24
27
  end
@@ -196,7 +199,7 @@ module Strata
196
199
  def meta(ds_key, table_name)
197
200
  say "\n● Schema for table: #{table_name} (#{ds_key}):\n", :yellow
198
201
  adapter = create_adapter(ds_key)
199
- md = adapter.metadata(table_name, **options.transform_keys { it.to_sym })
202
+ md = adapter.metadata(table_name, **options.transform_keys(&:to_sym))
200
203
 
201
204
  headings = md.columns.first.to_h.keys
202
205
  rows = md.columns.map(&:to_h).map(&:values)
@@ -254,6 +257,7 @@ module Strata
254
257
  unless expanded.start_with?(project_root)
255
258
  raise Strata::CommandError, "File path must be within project directory"
256
259
  end
260
+
257
261
  expanded
258
262
  end
259
263
 
@@ -369,6 +373,11 @@ module Strata
369
373
  strata_file = Configuration::STRATA_CONFIG_FILE
370
374
  File.chmod(0o600, strata_file) if File.exist?(strata_file)
371
375
 
376
+ if Utils::Git.git_repo?
377
+ Utils::Git.commit_file(Configuration::STRATA_CONFIG_FILE, "[Strata-CLI] Add AI config to .strata",
378
+ Dir.pwd)
379
+ end
380
+
372
381
  say "\n✔ AI configured with #{provider}", :green
373
382
  say " Note: API key is stored securely in .strata (not committed to repo)", :cyan
374
383
  end
@@ -58,7 +58,9 @@ module Strata
58
58
  import_commit_hash = Utils::ImportManager.generate_import_commit_hash(project_path)
59
59
  say "\nExternal imports change found. Proceeding with deployment", ColorHelper.info
60
60
  metadata[:commit] = "imports-#{import_commit_hash}"
61
- metadata[:commit_message] = "External imports updated: #{refreshed_imports.map { |c| File.basename(c[:source]) }.join(", ")}"
61
+ metadata[:commit_message] = "External imports updated: #{refreshed_imports.map do |c|
62
+ File.basename(c[:source])
63
+ end.join(", ")}"
62
64
  end
63
65
 
64
66
  archive_path = create_and_upload_archive(last_deployment_commit, refreshed_imports: refreshed_imports)
@@ -90,13 +92,14 @@ module Strata
90
92
 
91
93
  deployment_id = deployment["id"]
92
94
  has_tests = test_files_exist?
93
- monitor = Utils::DeploymentMonitor.new(client, config["project_id"], branch_id, deployment_id, has_tests: has_tests)
95
+ monitor = Utils::DeploymentMonitor.new(client, config["project_id"], branch_id, deployment_id,
96
+ has_tests: has_tests)
94
97
 
95
98
  # Show current status
96
99
  result = monitor.display_status
97
100
 
98
101
  # Continue monitoring if deployment is in progress, or if tests are expected but not yet available
99
- should_continue_monitoring = if result && !["succeeded", "failed"].include?(result["status"])
102
+ should_continue_monitoring = if result && !%w[succeeded failed].include?(result["status"])
100
103
  true
101
104
  elsif result && result["status"] == "succeeded" && result["stage"] == "finished"
102
105
  # Check if tests are expected but not yet available
@@ -122,25 +125,52 @@ module Strata
122
125
 
123
126
  def validate_deployment_configuration(config)
124
127
  ensure_api_key(config)
125
- validate_server_url(config)
128
+ ensure_server(config)
126
129
  ensure_git_url_populated
127
130
  ensure_project_id(config)
128
131
  end
129
132
 
130
133
  def ensure_api_key(config)
131
- return if config["api_key"] && !config["api_key"].to_s.strip.empty? && config["api_key"] != "YOUR_STRATA_API_KEY"
134
+ if config["api_key"] && !config["api_key"].to_s.strip.empty? && config["api_key"] != "YOUR_STRATA_API_KEY"
135
+ return
136
+ end
132
137
 
133
138
  config["api_key"] = collect_api_key_interactively
134
139
  end
135
140
 
136
- def validate_server_url(config)
137
- server_url = config["server"]
138
- return if server_url && !server_url.to_s.strip.empty?
141
+ def ensure_server(config)
142
+ return if config["server"] && !config["server"].to_s.strip.empty?
143
+
144
+ config["server"] = collect_server_interactively
145
+ end
146
+
147
+ def collect_server_interactively
148
+ prompt = TTY::Prompt.new
149
+
150
+ say "\nServer URL not found (not in project.yml)", :yellow
151
+ say "You can add it to project.yml or enter it now here\n", :cyan
152
+
153
+ server = prompt.ask("Enter Strata server URL:") do |q|
154
+ q.required true
155
+ q.validate(/\S+/, "Server URL cannot be empty")
156
+ end
157
+
158
+ server = server.strip.sub(%r{/\z}, "") # normalize: remove trailing slash
159
+
160
+ with_spinner("Saving server URL to project.yml") do
161
+ save_server_to_project_yml(server)
162
+ end
139
163
 
140
- env_msg = options[:environment] ? " (or in '#{options[:environment]}' environment section)" : ""
141
- say "\nServer URL not configured in project.yml#{env_msg}", :yellow
142
- say "Please add or update the 'server' field in project.yml and try again.\n", :white
143
- exit(1)
164
+ server
165
+ end
166
+
167
+ def save_server_to_project_yml(server)
168
+ Helpers::ProjectHelper.persist_server_to_project_yml(server, project_yml_path: File.join(project_path, Configuration::PROJECT_CONFIG_FILE))
169
+ CLI.config.reload!
170
+ return unless Utils::Git.git_repo?
171
+
172
+ Utils::Git.commit_file(Configuration::PROJECT_CONFIG_FILE, "[Strata-CLI] Add server URL to project.yml",
173
+ project_path)
144
174
  end
145
175
 
146
176
  def ensure_git_url_populated
@@ -148,13 +178,13 @@ module Strata
148
178
 
149
179
  CLI.config.reload!
150
180
 
151
- if Utils::Git.git_repo?
152
- Utils::Git.commit_file(
153
- Configuration::PROJECT_CONFIG_FILE,
154
- "[Strata-CLI] Auto-populate git URL from remote",
155
- project_path
156
- )
157
- end
181
+ return unless Utils::Git.git_repo?
182
+
183
+ Utils::Git.commit_file(
184
+ Configuration::PROJECT_CONFIG_FILE,
185
+ "[Strata-CLI] Auto-populate git URL from remote",
186
+ project_path
187
+ )
158
188
  end
159
189
 
160
190
  def ensure_project_id(config)
@@ -182,41 +212,50 @@ module Strata
182
212
  save_api_key_to_strata(api_key)
183
213
  end
184
214
 
215
+ if Utils::Git.git_repo?
216
+ Utils::Git.commit_file(Configuration::STRATA_CONFIG_FILE, "[Strata-CLI] Save API key to .strata",
217
+ project_path)
218
+ end
219
+
185
220
  api_key
186
221
  end
187
222
 
188
223
  def save_api_key_to_strata(api_key)
224
+ save_key_to_strata("api_key", api_key)
225
+ end
226
+
227
+ def save_key_to_strata(key, value)
189
228
  strata_file = Configuration::STRATA_CONFIG_FILE
190
229
 
191
230
  # Read existing content if file exists
192
231
  existing_content = File.exist?(strata_file) ? File.read(strata_file) : ""
193
232
 
194
233
  begin
195
- if /^api_key:\s*.+$/m.match?(existing_content)
196
- updated_content = existing_content.gsub(/^(\s*)api_key:\s*.+$/, "\\1api_key: #{api_key}")
234
+ if /^#{Regexp.escape(key)}:\s*.+$/m.match?(existing_content)
235
+ updated_content = existing_content.gsub(/^(\s*)#{Regexp.escape(key)}:\s*.+$/, "\\1#{key}: #{value}")
197
236
  File.write(strata_file, updated_content)
198
237
  else
199
238
  File.open(strata_file, "a") do |f|
200
239
  f.puts "\n" unless existing_content.empty? || existing_content.end_with?("\n")
201
- f.puts "api_key: #{api_key}"
240
+ f.puts "#{key}: #{value}"
202
241
  end
203
242
  end
204
243
 
205
244
  # Set restrictive permissions (read/write for owner only)
206
245
  File.chmod(0o600, strata_file) if File.exist?(strata_file)
207
246
 
208
- # Reload config to pick up the new API key
247
+ # Reload config to pick up the new value
209
248
  CLI.config.reload!
210
249
  rescue Errno::EACCES => e
211
250
  raise Strata::CommandError, "Permission denied writing to #{strata_file}: #{e.message}"
212
251
  rescue Errno::ENOSPC => e
213
252
  raise Strata::CommandError, "Disk full: #{e.message}"
214
253
  rescue => e
215
- raise Strata::CommandError, "Failed to write API key to #{strata_file}: #{e.message}"
254
+ raise Strata::CommandError, "Failed to write #{key} to #{strata_file}: #{e.message}"
216
255
  end
217
256
  end
218
257
 
219
- def handle_missing_deployment_config(config, key)
258
+ def handle_missing_deployment_config(_config, key)
220
259
  raise Strata::CommandError, "Missing required configuration: #{key}. Check your project.yml file."
221
260
  end
222
261
 
@@ -248,9 +287,11 @@ module Strata
248
287
  commit_status = Utils::Git.check_commit_status(branch_name)
249
288
  case commit_status[:status]
250
289
  when :behind
251
- raise Strata::CommandError, "Your local branch is behind remote. Please pull latest changes before deploying."
290
+ raise Strata::CommandError,
291
+ "Your local branch is behind remote. Please pull latest changes before deploying."
252
292
  when :diverged
253
- raise Strata::CommandError, "Your local branch has diverged from remote. Please sync your branch before deploying."
293
+ raise Strata::CommandError,
294
+ "Your local branch has diverged from remote. Please sync your branch before deploying."
254
295
  when :ahead, :same
255
296
  # Status is already shown via spinner, no need for additional message
256
297
  end
@@ -299,9 +340,7 @@ module Strata
299
340
  end.select { |path| File.exist?(path) }
300
341
 
301
342
  change_count = files_to_include.length
302
- if refreshed_imports.any?
303
- change_count += refreshed_imports.length
304
- end
343
+ change_count += refreshed_imports.length if refreshed_imports.any?
305
344
 
306
345
  say "Including #{change_count} changed file(s) in archive...\n", ColorHelper.info
307
346
  Utils::Archive.create(project_path, files_to_include: files_to_include)
@@ -327,7 +366,8 @@ module Strata
327
366
  client = API::Client.new(config["server"], config["api_key"])
328
367
 
329
368
  has_tests = test_files_exist?
330
- Utils::DeploymentMonitor.new(client, config["project_id"], branch_id, deployment_id, has_tests: has_tests).start
369
+ Utils::DeploymentMonitor.new(client, config["project_id"], branch_id, deployment_id,
370
+ has_tests: has_tests).start
331
371
  end
332
372
 
333
373
  def test_files_exist?
@@ -353,7 +393,7 @@ module Strata
353
393
  exit(0)
354
394
  end
355
395
 
356
- def get_production_branch(config)
396
+ def get_production_branch(_config)
357
397
  return nil unless File.exist?("project.yml")
358
398
 
359
399
  project_config = YAML.safe_load_file("project.yml", permitted_classes: [Date, Time], aliases: true) || {}
@@ -390,13 +430,12 @@ module Strata
390
430
  git = project_config["git"]
391
431
  production_branch = project_config["production_branch"]
392
432
 
393
- unless name
394
- raise Strata::CommandError, "Cannot create project: name is required in project.yml"
395
- end
433
+ raise Strata::CommandError, "Cannot create project: name is required in project.yml" unless name
396
434
 
397
435
  with_spinner("Creating project '#{name}' on server") do
398
436
  client = API::Client.new(config["server"], config["api_key"])
399
- project = client.create_project(name, uid, description: description, git: git, production_branch: production_branch)
437
+ project = client.create_project(name, uid, description: description, git: git,
438
+ production_branch: production_branch)
400
439
  project["id"]
401
440
  end
402
441
  end
@@ -405,6 +444,10 @@ module Strata
405
444
  with_spinner("Persisting project ID to project.yml") do
406
445
  Helpers::ProjectHelper.persist_project_id_to_yml(project_id)
407
446
  end
447
+ return unless Utils::Git.git_repo?
448
+
449
+ Utils::Git.commit_file(Configuration::PROJECT_CONFIG_FILE, "[Strata-CLI] Persist project ID to project.yml",
450
+ project_path)
408
451
  end
409
452
 
410
453
  def refresh_external_imports
@@ -16,9 +16,7 @@ module Strata
16
16
 
17
17
  desc "link PROJECT_ID", "Link local project to an existing project on the server"
18
18
  def link(project_id)
19
- unless project_id && !project_id.to_s.strip.empty?
20
- raise Strata::CommandError, "Project ID is required."
21
- end
19
+ raise Strata::CommandError, "Project ID is required." unless project_id && !project_id.to_s.strip.empty?
22
20
 
23
21
  project_yml_path = "project.yml"
24
22
  project_config = YAML.safe_load_file(project_yml_path, permitted_classes: [Date, Time], aliases: true) || {}
@@ -28,9 +26,9 @@ module Strata
28
26
  return
29
27
  end
30
28
 
31
- if Helpers::ProjectHelper.persist_project_id_to_yml(project_id, project_yml_path: project_yml_path)
32
- say "✓ Project linked successfully. project_id: #{project_id}", ColorHelper.info
33
- end
29
+ return unless Helpers::ProjectHelper.persist_project_id_to_yml(project_id, project_yml_path: project_yml_path)
30
+
31
+ say "✓ Project linked successfully. project_id: #{project_id}", ColorHelper.info
34
32
  end
35
33
  end
36
34
  end
@@ -44,7 +44,7 @@ module Strata
44
44
  name = File.basename(file, ".yml")
45
45
  content = File.read(file)
46
46
  desc = content.match(/^# Description: (.+)$/)&.[](1) || ""
47
- desc = desc[0..50] + "..." if desc.length > 50
47
+ desc = "#{desc[0..50]}..." if desc.length > 50
48
48
 
49
49
  if desc.empty?
50
50
  say " #{name}", :white
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "tty-spinner"
2
4
  require "pastel"
3
5
  require "tty-table"
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "tty-prompt"
2
4
  require_relative "../helpers/table_filter"
3
5
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "tty-prompt"
2
4
  require "tty-table"
3
5
  require "pastel"
@@ -143,6 +145,10 @@ module Strata
143
145
  return if field[:expression] == "back"
144
146
 
145
147
  field[:schema_type] = prompt.select(" Type:", %w[dimension measure], default: field[:schema_type])
148
+
149
+ current_synonyms = (field[:synonyms] || []).join(", ")
150
+ synonyms_input = prompt.ask(" Synonyms (comma-separated):", default: current_synonyms)
151
+ field[:synonyms] = synonyms_input.to_s.split(",").map(&:strip).reject(&:empty?)
146
152
  rescue TTY::Reader::InputInterrupt
147
153
  # User pressed Ctrl+C, go back without saving
148
154
  nil
@@ -46,9 +46,7 @@ module Strata
46
46
  ]
47
47
 
48
48
  required_files.each do |required_file|
49
- if File.exist?(required_file) && !files.include?(required_file)
50
- files << required_file
51
- end
49
+ files << required_file if File.exist?(required_file) && !files.include?(required_file)
52
50
  end
53
51
 
54
52
  # Always include all test files
@@ -15,26 +15,26 @@ module Strata
15
15
  include Terminal
16
16
 
17
17
  # Terminal statuses that indicate deployment is complete
18
- TERMINAL_STATUSES = ["succeeded", "failed"].freeze
18
+ TERMINAL_STATUSES = %w[succeeded failed].freeze
19
19
  NOT_STARTED_STAGE = "not_started"
20
20
  DEFAULT_POLL_INTERVAL = 0.5 # seconds
21
21
  DEFAULT_TIMEOUT = 600 # seconds (10 minutes)
22
22
  TEST_WAIT_TIMEOUT = 5 # seconds
23
- STAGES = [
24
- :not_started,
25
- :preparing,
26
- :pre_migrations,
27
- :project_configuration,
28
- :processing_datasources,
29
- :processing_models,
30
- :removing_deleted_models,
31
- :processing_relationships,
32
- :forming_universes,
33
- :creating_blend_paths,
34
- :validating_references,
35
- :post_migrations,
36
- :cleaning_up,
37
- :finished
23
+ STAGES = %i[
24
+ not_started
25
+ preparing
26
+ pre_migrations
27
+ project_configuration
28
+ processing_datasources
29
+ processing_models
30
+ removing_deleted_models
31
+ processing_relationships
32
+ forming_universes
33
+ creating_blend_paths
34
+ validating_references
35
+ post_migrations
36
+ cleaning_up
37
+ finished
38
38
  ].freeze
39
39
  TESTING_STAGE = :running_tests
40
40
 
@@ -127,7 +127,7 @@ module Strata
127
127
  rescue Interrupt
128
128
  stop_all_spinners
129
129
  say "\n\n Monitoring interrupted. Deployment continues in background.", ColorHelper.warning
130
- say " Check server for deployment status.\n", ColorHelper.info
130
+ say " Check server for deployment status with command `strata deploy status`.\n", ColorHelper.info
131
131
  nil
132
132
  rescue => e
133
133
  stop_all_spinners
@@ -145,9 +145,7 @@ module Strata
145
145
 
146
146
  process_deployment_state(deployment)
147
147
 
148
- if terminal_status?(deployment_value(deployment, "status"))
149
- display_final_status(deployment)
150
- end
148
+ display_final_status(deployment) if terminal_status?(deployment_value(deployment, "status"))
151
149
 
152
150
  deployment
153
151
  rescue Interrupt
@@ -324,6 +322,7 @@ module Strata
324
322
  final_stage_string = final_stage&.to_s
325
323
  @spinners.each_key do |stage_string|
326
324
  next if stage_string == TESTING_STAGE # Handle test spinner separately
325
+
327
326
  complete_stage(stage_string) if stage_string != final_stage_string
328
327
  end
329
328
  end
@@ -385,9 +384,7 @@ module Strata
385
384
  end
386
385
 
387
386
  def stop_all_spinners
388
- @spinners.each do |_stage_string, spinner|
389
- spinner.stop
390
- end
387
+ @spinners.each_value(&:stop)
391
388
  @spinners.clear
392
389
  @tests_running = false
393
390
  end
@@ -396,11 +393,16 @@ module Strata
396
393
  raise ArgumentError, "client cannot be nil" if client.nil?
397
394
  raise ArgumentError, "project_id cannot be nil or empty" if project_id.nil? || project_id.to_s.strip.empty?
398
395
  raise ArgumentError, "branch_id cannot be nil or empty" if branch_id.nil? || branch_id.to_s.strip.empty?
399
- raise ArgumentError, "deployment_id cannot be nil or empty" if deployment_id.nil? || deployment_id.to_s.strip.empty?
396
+
397
+ return unless deployment_id.nil? || deployment_id.to_s.strip.empty?
398
+
399
+ raise ArgumentError,
400
+ "deployment_id cannot be nil or empty"
400
401
  end
401
402
 
402
403
  def deployment_value(deployment, key)
403
404
  return nil unless deployment.is_a?(Hash)
405
+
404
406
  deployment[key] || deployment[key.to_sym]
405
407
  end
406
408
 
@@ -430,7 +432,7 @@ module Strata
430
432
 
431
433
  test_results = deployment_value(latest_test_run, "test_results")
432
434
  if test_results
433
- say "\n" + "=" * 60, :border
435
+ say "\n#{"=" * 60}", :border
434
436
  complete_stage(TESTING_STAGE)
435
437
  display_test_results(test_results, skip_opening_divider: true)
436
438
  else
@@ -11,6 +11,7 @@ module Strata
11
11
  def validate_commit_hash(commit_hash)
12
12
  return false unless commit_hash.is_a?(String)
13
13
  return false unless commit_hash.match?(/\A[a-f0-9]{7,40}\z/i)
14
+
14
15
  true
15
16
  end
16
17
 
@@ -127,7 +128,8 @@ module Strata
127
128
 
128
129
  return if git_available?
129
130
 
130
- Thor::Shell::Color.new.say_error "ERROR: Git is required but not found. Please install Git to use strata-cli.", :red
131
+ Thor::Shell::Color.new.say_error "ERROR: Git is required but not found. Please install Git to use strata-cli.",
132
+ :red
131
133
  exit 1
132
134
  end
133
135
 
@@ -143,7 +145,7 @@ module Strata
143
145
  end
144
146
 
145
147
  def run_git_command(*args)
146
- stdout, _, _ = Open3.capture3("git", *args)
148
+ stdout, = Open3.capture3("git", *args)
147
149
  stdout&.strip
148
150
  end
149
151
 
@@ -190,7 +192,7 @@ module Strata
190
192
  def uncommitted_changes?
191
193
  return false unless git_repo?
192
194
 
193
- stdout, _, _ = Open3.capture3("git", "status", "--porcelain")
195
+ stdout, = Open3.capture3("git", "status", "--porcelain")
194
196
  !stdout.strip.empty?
195
197
  end
196
198
 
@@ -219,17 +221,18 @@ module Strata
219
221
 
220
222
  # Check if local is ahead, behind, or diverged
221
223
  # rev-list --left-right shows: > = commits in local not in remote (ahead), < = commits in remote not in local (behind)
222
- diff_output, _, _ = Open3.capture3("git", "rev-list", "--left-right", "#{remote_sha}...#{local_sha}")
224
+ diff_output, = Open3.capture3("git", "rev-list", "--left-right", "#{remote_sha}...#{local_sha}")
223
225
 
224
226
  ahead_count = diff_output.lines.count { |line| line.start_with?(">") }
225
227
  behind_count = diff_output.lines.count { |line| line.start_with?("<") }
226
228
 
227
- if ahead_count > 0 && behind_count == 0
229
+ if ahead_count.positive? && behind_count.zero?
228
230
  {status: :ahead, message: "Local branch is #{ahead_count} commit(s) ahead of remote"}
229
- elsif ahead_count == 0 && behind_count > 0
231
+ elsif ahead_count.zero? && behind_count.positive?
230
232
  {status: :behind, message: "Local branch is #{behind_count} commit(s) behind remote"}
231
233
  else
232
- {status: :diverged, message: "Local branch has diverged from remote (#{ahead_count} ahead, #{behind_count} behind)"}
234
+ {status: :diverged,
235
+ message: "Local branch has diverged from remote (#{ahead_count} ahead, #{behind_count} behind)"}
233
236
  end
234
237
  end
235
238