strata-cli 0.1.4.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/lib/strata/cli/api/client.rb +4 -4
- data/lib/strata/cli/api/connection_error_handler.rb +1 -1
- data/lib/strata/cli/configuration.rb +3 -1
- data/lib/strata/cli/credentials.rb +2 -0
- data/lib/strata/cli/descriptions/create/migration.txt +10 -0
- data/lib/strata/cli/descriptions/create/relation.txt +6 -0
- data/lib/strata/cli/descriptions/create/table.txt +13 -0
- data/lib/strata/cli/descriptions/datasource/add.txt +6 -0
- data/lib/strata/cli/descriptions/datasource/meta.txt +5 -0
- data/lib/strata/cli/descriptions/datasource/tables.txt +6 -0
- data/lib/strata/cli/descriptions/datasource/test.txt +5 -0
- data/lib/strata/cli/descriptions/deploy/deploy.txt +13 -0
- data/lib/strata/cli/descriptions/deploy/status.txt +3 -0
- data/lib/strata/cli/descriptions/init.txt +6 -0
- data/lib/strata/cli/error_reporter.rb +70 -0
- data/lib/strata/cli/generators/datasource.rb +4 -1
- data/lib/strata/cli/generators/group.rb +14 -8
- data/lib/strata/cli/generators/migration.rb +1 -1
- data/lib/strata/cli/generators/project.rb +146 -130
- data/lib/strata/cli/generators/table.rb +1 -3
- data/lib/strata/cli/guard.rb +2 -0
- data/lib/strata/cli/helpers/command_context.rb +23 -4
- data/lib/strata/cli/helpers/datasource_helper.rb +17 -1
- data/lib/strata/cli/helpers/description_helper.rb +2 -0
- data/lib/strata/cli/helpers/prompts.rb +2 -0
- data/lib/strata/cli/main.rb +5 -1
- data/lib/strata/cli/sub_commands/audit.rb +19 -5
- data/lib/strata/cli/sub_commands/create.rb +41 -19
- data/lib/strata/cli/sub_commands/datasource.rb +9 -3
- data/lib/strata/cli/sub_commands/deploy.rb +40 -26
- data/lib/strata/cli/sub_commands/project.rb +4 -6
- data/lib/strata/cli/sub_commands/table.rb +1 -1
- data/lib/strata/cli/terminal.rb +2 -0
- data/lib/strata/cli/ui/autocomplete.rb +2 -0
- data/lib/strata/cli/ui/field_editor.rb +2 -0
- data/lib/strata/cli/utils/archive.rb +1 -3
- data/lib/strata/cli/utils/deployment_monitor.rb +26 -24
- data/lib/strata/cli/utils/git.rb +10 -7
- data/lib/strata/cli/utils/import_manager.rb +21 -23
- data/lib/strata/cli/utils/test_reporter.rb +16 -25
- data/lib/strata/cli/utils/version_checker.rb +4 -6
- data/lib/strata/cli/utils/yaml_import_resolver.rb +5 -2
- data/lib/strata/cli/utils.rb +2 -0
- data/lib/strata/cli/version.rb +1 -1
- data/lib/strata/cli.rb +8 -0
- metadata +29 -27
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative "../guard"
|
|
2
4
|
require_relative "../credentials"
|
|
3
5
|
require_relative "../terminal"
|
|
@@ -19,7 +21,7 @@ module Strata
|
|
|
19
21
|
desc "adapters", "Lists supported data warehouse adapters"
|
|
20
22
|
def adapters
|
|
21
23
|
say "\n\tSupported Adapters\n\n", :yellow
|
|
22
|
-
DWH.adapters.
|
|
24
|
+
DWH.adapters.each_key do
|
|
23
25
|
say "\t\t● #{it}", :magenta
|
|
24
26
|
end
|
|
25
27
|
end
|
|
@@ -197,7 +199,7 @@ module Strata
|
|
|
197
199
|
def meta(ds_key, table_name)
|
|
198
200
|
say "\n● Schema for table: #{table_name} (#{ds_key}):\n", :yellow
|
|
199
201
|
adapter = create_adapter(ds_key)
|
|
200
|
-
md = adapter.metadata(table_name, **options.transform_keys
|
|
202
|
+
md = adapter.metadata(table_name, **options.transform_keys(&:to_sym))
|
|
201
203
|
|
|
202
204
|
headings = md.columns.first.to_h.keys
|
|
203
205
|
rows = md.columns.map(&:to_h).map(&:values)
|
|
@@ -255,6 +257,7 @@ module Strata
|
|
|
255
257
|
unless expanded.start_with?(project_root)
|
|
256
258
|
raise Strata::CommandError, "File path must be within project directory"
|
|
257
259
|
end
|
|
260
|
+
|
|
258
261
|
expanded
|
|
259
262
|
end
|
|
260
263
|
|
|
@@ -370,7 +373,10 @@ module Strata
|
|
|
370
373
|
strata_file = Configuration::STRATA_CONFIG_FILE
|
|
371
374
|
File.chmod(0o600, strata_file) if File.exist?(strata_file)
|
|
372
375
|
|
|
373
|
-
|
|
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
|
|
374
380
|
|
|
375
381
|
say "\n✔ AI configured with #{provider}", :green
|
|
376
382
|
say " Note: API key is stored securely in .strata (not committed to repo)", :cyan
|
|
@@ -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
|
|
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,
|
|
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 &&
|
|
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
|
|
@@ -128,7 +131,9 @@ module Strata
|
|
|
128
131
|
end
|
|
129
132
|
|
|
130
133
|
def ensure_api_key(config)
|
|
131
|
-
|
|
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
|
|
@@ -162,7 +167,10 @@ module Strata
|
|
|
162
167
|
def save_server_to_project_yml(server)
|
|
163
168
|
Helpers::ProjectHelper.persist_server_to_project_yml(server, project_yml_path: File.join(project_path, Configuration::PROJECT_CONFIG_FILE))
|
|
164
169
|
CLI.config.reload!
|
|
165
|
-
|
|
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)
|
|
166
174
|
end
|
|
167
175
|
|
|
168
176
|
def ensure_git_url_populated
|
|
@@ -170,13 +178,13 @@ module Strata
|
|
|
170
178
|
|
|
171
179
|
CLI.config.reload!
|
|
172
180
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
+
)
|
|
180
188
|
end
|
|
181
189
|
|
|
182
190
|
def ensure_project_id(config)
|
|
@@ -204,7 +212,10 @@ module Strata
|
|
|
204
212
|
save_api_key_to_strata(api_key)
|
|
205
213
|
end
|
|
206
214
|
|
|
207
|
-
|
|
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
|
|
208
219
|
|
|
209
220
|
api_key
|
|
210
221
|
end
|
|
@@ -244,7 +255,7 @@ module Strata
|
|
|
244
255
|
end
|
|
245
256
|
end
|
|
246
257
|
|
|
247
|
-
def handle_missing_deployment_config(
|
|
258
|
+
def handle_missing_deployment_config(_config, key)
|
|
248
259
|
raise Strata::CommandError, "Missing required configuration: #{key}. Check your project.yml file."
|
|
249
260
|
end
|
|
250
261
|
|
|
@@ -276,9 +287,11 @@ module Strata
|
|
|
276
287
|
commit_status = Utils::Git.check_commit_status(branch_name)
|
|
277
288
|
case commit_status[:status]
|
|
278
289
|
when :behind
|
|
279
|
-
raise Strata::CommandError,
|
|
290
|
+
raise Strata::CommandError,
|
|
291
|
+
"Your local branch is behind remote. Please pull latest changes before deploying."
|
|
280
292
|
when :diverged
|
|
281
|
-
raise Strata::CommandError,
|
|
293
|
+
raise Strata::CommandError,
|
|
294
|
+
"Your local branch has diverged from remote. Please sync your branch before deploying."
|
|
282
295
|
when :ahead, :same
|
|
283
296
|
# Status is already shown via spinner, no need for additional message
|
|
284
297
|
end
|
|
@@ -327,9 +340,7 @@ module Strata
|
|
|
327
340
|
end.select { |path| File.exist?(path) }
|
|
328
341
|
|
|
329
342
|
change_count = files_to_include.length
|
|
330
|
-
if refreshed_imports.any?
|
|
331
|
-
change_count += refreshed_imports.length
|
|
332
|
-
end
|
|
343
|
+
change_count += refreshed_imports.length if refreshed_imports.any?
|
|
333
344
|
|
|
334
345
|
say "Including #{change_count} changed file(s) in archive...\n", ColorHelper.info
|
|
335
346
|
Utils::Archive.create(project_path, files_to_include: files_to_include)
|
|
@@ -355,7 +366,8 @@ module Strata
|
|
|
355
366
|
client = API::Client.new(config["server"], config["api_key"])
|
|
356
367
|
|
|
357
368
|
has_tests = test_files_exist?
|
|
358
|
-
Utils::DeploymentMonitor.new(client, config["project_id"], branch_id, deployment_id,
|
|
369
|
+
Utils::DeploymentMonitor.new(client, config["project_id"], branch_id, deployment_id,
|
|
370
|
+
has_tests: has_tests).start
|
|
359
371
|
end
|
|
360
372
|
|
|
361
373
|
def test_files_exist?
|
|
@@ -381,7 +393,7 @@ module Strata
|
|
|
381
393
|
exit(0)
|
|
382
394
|
end
|
|
383
395
|
|
|
384
|
-
def get_production_branch(
|
|
396
|
+
def get_production_branch(_config)
|
|
385
397
|
return nil unless File.exist?("project.yml")
|
|
386
398
|
|
|
387
399
|
project_config = YAML.safe_load_file("project.yml", permitted_classes: [Date, Time], aliases: true) || {}
|
|
@@ -418,13 +430,12 @@ module Strata
|
|
|
418
430
|
git = project_config["git"]
|
|
419
431
|
production_branch = project_config["production_branch"]
|
|
420
432
|
|
|
421
|
-
unless name
|
|
422
|
-
raise Strata::CommandError, "Cannot create project: name is required in project.yml"
|
|
423
|
-
end
|
|
433
|
+
raise Strata::CommandError, "Cannot create project: name is required in project.yml" unless name
|
|
424
434
|
|
|
425
435
|
with_spinner("Creating project '#{name}' on server") do
|
|
426
436
|
client = API::Client.new(config["server"], config["api_key"])
|
|
427
|
-
project = client.create_project(name, uid, description: description, git: git,
|
|
437
|
+
project = client.create_project(name, uid, description: description, git: git,
|
|
438
|
+
production_branch: production_branch)
|
|
428
439
|
project["id"]
|
|
429
440
|
end
|
|
430
441
|
end
|
|
@@ -433,7 +444,10 @@ module Strata
|
|
|
433
444
|
with_spinner("Persisting project ID to project.yml") do
|
|
434
445
|
Helpers::ProjectHelper.persist_project_id_to_yml(project_id)
|
|
435
446
|
end
|
|
436
|
-
|
|
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)
|
|
437
451
|
end
|
|
438
452
|
|
|
439
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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]
|
|
47
|
+
desc = "#{desc[0..50]}..." if desc.length > 50
|
|
48
48
|
|
|
49
49
|
if desc.empty?
|
|
50
50
|
say " #{name}", :white
|
data/lib/strata/cli/terminal.rb
CHANGED
|
@@ -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 = [
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
|
@@ -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.
|
|
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
|
-
|
|
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"
|
|
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
|
data/lib/strata/cli/utils/git.rb
CHANGED
|
@@ -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.",
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
|
@@ -16,9 +16,9 @@ module Strata
|
|
|
16
16
|
LOCK_FILE = "strata.lock"
|
|
17
17
|
|
|
18
18
|
def validate_import_path(import_path)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
return unless Pathname.new(import_path).absolute?
|
|
20
|
+
|
|
21
|
+
raise Strata::InvalidImportPathError, "Import paths must be relative, not absolute: #{import_path}"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
24
|
def track_external_import(import_path, resolved_path, project_path)
|
|
@@ -58,9 +58,7 @@ module Strata
|
|
|
58
58
|
|
|
59
59
|
resolved_path = File.expand_path(import_path, base_dir)
|
|
60
60
|
|
|
61
|
-
unless File.exist?(resolved_path)
|
|
62
|
-
raise Strata::MissingImportError, "Import file not found: #{import_path}"
|
|
63
|
-
end
|
|
61
|
+
raise Strata::MissingImportError, "Import file not found: #{import_path}" unless File.exist?(resolved_path)
|
|
64
62
|
|
|
65
63
|
# Track external imports in lock file
|
|
66
64
|
track_external_import(import_path, resolved_path, project_path)
|
|
@@ -77,14 +75,14 @@ module Strata
|
|
|
77
75
|
next unless entry["source"] && File.exist?(entry["source"])
|
|
78
76
|
|
|
79
77
|
current_hash = file_hash(entry["source"])
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
78
|
+
next unless current_hash != entry["hash"]
|
|
79
|
+
|
|
80
|
+
changed << {
|
|
81
|
+
path: entry["path"],
|
|
82
|
+
source: entry["source"],
|
|
83
|
+
old_hash: entry["hash"],
|
|
84
|
+
new_hash: current_hash
|
|
85
|
+
}
|
|
88
86
|
end
|
|
89
87
|
|
|
90
88
|
changed
|
|
@@ -99,15 +97,15 @@ module Strata
|
|
|
99
97
|
next unless entry["source"] && File.exist?(entry["source"])
|
|
100
98
|
|
|
101
99
|
current_hash = file_hash(entry["source"])
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
100
|
+
next unless current_hash != entry["hash"]
|
|
101
|
+
|
|
102
|
+
entry["hash"] = current_hash
|
|
103
|
+
entry["imported_at"] = Time.now.utc.iso8601
|
|
104
|
+
refreshed << {
|
|
105
|
+
path: entry["path"],
|
|
106
|
+
source: entry["source"],
|
|
107
|
+
hash: current_hash
|
|
108
|
+
}
|
|
111
109
|
end
|
|
112
110
|
|
|
113
111
|
save_lock_file(project_path, lock_data) if refreshed.any?
|
|
@@ -58,15 +58,15 @@ module Strata
|
|
|
58
58
|
failed = tests.count { |t| t["status"] == "failed" }
|
|
59
59
|
errors = tests.count { |t| t["status"] == "error" }
|
|
60
60
|
|
|
61
|
-
say "\n"
|
|
61
|
+
say "\n#{"=" * 60}", :border unless skip_opening_divider
|
|
62
62
|
say "\n Tests ran: #{total} total, #{passed} passed", :info
|
|
63
|
-
say " #{failed} failed, #{errors} error#{"s" if errors != 1}" if failed
|
|
64
|
-
say "\n"
|
|
63
|
+
say " #{failed} failed, #{errors} error#{"s" if errors != 1}" if failed.positive? || errors.positive?
|
|
64
|
+
say "\n#{"=" * 60}", :border
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
def display_failures(test_results)
|
|
68
68
|
tests = test_results["tests"] || []
|
|
69
|
-
failed_tests = tests.select { |t|
|
|
69
|
+
failed_tests = tests.select { |t| %w[failed error].include?(t["status"]) }
|
|
70
70
|
|
|
71
71
|
return if failed_tests.empty?
|
|
72
72
|
|
|
@@ -89,41 +89,32 @@ module Strata
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
# Display assertion failure details
|
|
92
|
-
if test["assertion_type"]
|
|
93
|
-
say " Assertion failed: #{test["assertion_type"]}", :error
|
|
94
|
-
end
|
|
92
|
+
say " Assertion failed: #{test["assertion_type"]}", :error if test["assertion_type"]
|
|
95
93
|
|
|
96
|
-
if test["message"]
|
|
97
|
-
say " #{test["message"]}", :error
|
|
98
|
-
end
|
|
94
|
+
say " #{test["message"]}", :error if test["message"]
|
|
99
95
|
|
|
100
96
|
# Show expected vs generated SQL for debugging
|
|
101
|
-
if test["expected_sql"]
|
|
102
|
-
say " Expected: #{truncate(test["expected_sql"], 100)}", :error
|
|
103
|
-
end
|
|
97
|
+
say " Expected: #{truncate(test["expected_sql"], 100)}", :error if test["expected_sql"]
|
|
104
98
|
|
|
105
|
-
if test["pattern"]
|
|
106
|
-
say " Pattern: #{test["pattern"]}", :error
|
|
107
|
-
end
|
|
99
|
+
say " Pattern: #{test["pattern"]}", :error if test["pattern"]
|
|
108
100
|
|
|
109
|
-
if test["generated_sql"]
|
|
110
|
-
say " Generated: #{truncate(test["generated_sql"], 200)}", :error
|
|
111
|
-
end
|
|
101
|
+
say " Generated: #{truncate(test["generated_sql"], 200)}", :error if test["generated_sql"]
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
103
|
+
return unless test["failing_values"].is_a?(Array) && test["failing_values"].any?
|
|
104
|
+
|
|
105
|
+
sample_values = test["failing_values"].first(5)
|
|
106
|
+
say " Sample failing values: #{sample_values.inspect}", :error
|
|
117
107
|
end
|
|
118
108
|
|
|
119
109
|
def truncate(str, max_length)
|
|
120
110
|
return str if str.nil? || str.length <= max_length
|
|
121
|
-
|
|
111
|
+
|
|
112
|
+
"#{str[0, max_length]}..."
|
|
122
113
|
end
|
|
123
114
|
|
|
124
115
|
def has_failures?(test_results)
|
|
125
116
|
tests = test_results["tests"] || []
|
|
126
|
-
tests.any? { |t|
|
|
117
|
+
tests.any? { |t| %w[failed error].include?(t["status"]) }
|
|
127
118
|
end
|
|
128
119
|
end
|
|
129
120
|
end
|
|
@@ -14,7 +14,7 @@ module Strata
|
|
|
14
14
|
module_function
|
|
15
15
|
|
|
16
16
|
GEM_NAME = "strata-cli"
|
|
17
|
-
RUBYGEMS_API_URL = "https://rubygems.org/api/v1/gems/#{GEM_NAME}.json"
|
|
17
|
+
RUBYGEMS_API_URL = "https://rubygems.org/api/v1/gems/#{GEM_NAME}.json".freeze
|
|
18
18
|
CACHE_FILE = File.join(ENV.fetch("TMPDIR", "/tmp"), "strata-cli-version-check-cache")
|
|
19
19
|
CACHE_DURATION = 86_400 # 24 hours in seconds
|
|
20
20
|
|
|
@@ -24,9 +24,7 @@ module Strata
|
|
|
24
24
|
latest_version = fetch_latest_version
|
|
25
25
|
return unless latest_version
|
|
26
26
|
|
|
27
|
-
if newer_version_available?(current_version, latest_version)
|
|
28
|
-
display_update_message(latest_version)
|
|
29
|
-
end
|
|
27
|
+
display_update_message(latest_version) if newer_version_available?(current_version, latest_version)
|
|
30
28
|
|
|
31
29
|
update_cache
|
|
32
30
|
rescue
|
|
@@ -40,8 +38,8 @@ module Strata
|
|
|
40
38
|
|
|
41
39
|
# Simple version comparison (assumes semantic versioning)
|
|
42
40
|
# Convert non-numeric parts (like "beta") to 0 for comparison
|
|
43
|
-
current_parts = current.split(".").map
|
|
44
|
-
latest_parts = latest.split(".").map
|
|
41
|
+
current_parts = current.split(".").map(&:to_i)
|
|
42
|
+
latest_parts = latest.split(".").map(&:to_i)
|
|
45
43
|
|
|
46
44
|
# Pad with zeros if needed
|
|
47
45
|
max_length = [current_parts.length, latest_parts.length].max
|
|
@@ -15,7 +15,10 @@ module Strata
|
|
|
15
15
|
return {} unless File.exist?(file_path)
|
|
16
16
|
|
|
17
17
|
absolute_path = File.expand_path(file_path)
|
|
18
|
-
|
|
18
|
+
if visited.include?(absolute_path)
|
|
19
|
+
raise Strata::CircularImportError,
|
|
20
|
+
"Circular import detected: #{format_cycle(visited, absolute_path)}"
|
|
21
|
+
end
|
|
19
22
|
|
|
20
23
|
visited.add(absolute_path)
|
|
21
24
|
content = YAML.safe_load_file(file_path, permitted_classes: [Date, Time], aliases: true)
|
|
@@ -24,7 +27,7 @@ module Strata
|
|
|
24
27
|
resolved_content = content.dup
|
|
25
28
|
resolved_content.delete("imports")
|
|
26
29
|
|
|
27
|
-
if content["imports"]
|
|
30
|
+
if content["imports"].is_a?(Array)
|
|
28
31
|
merged_fields = resolve_imports(content["imports"], file_path, project_path, visited)
|
|
29
32
|
resolved_content["fields"] = merge_fields(merged_fields, content["fields"] || [])
|
|
30
33
|
else
|
data/lib/strata/cli/utils.rb
CHANGED