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 "group"
|
|
2
4
|
require_relative "datasource"
|
|
3
5
|
require_relative "../helpers/project_helper"
|
|
@@ -6,185 +8,199 @@ require "faraday"
|
|
|
6
8
|
require "json"
|
|
7
9
|
require "uri"
|
|
8
10
|
|
|
9
|
-
module Strata
|
|
10
|
-
module
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
module Strata
|
|
12
|
+
module CLI
|
|
13
|
+
module Generators
|
|
14
|
+
class Project < Group
|
|
15
|
+
include API::ConnectionErrorHandler
|
|
16
|
+
|
|
17
|
+
BASE_SERVER_URL = "http://localhost:3000"
|
|
18
|
+
|
|
19
|
+
argument :name, type: :string, required: false, desc: "The name of the project. Optional when using --source."
|
|
20
|
+
class_option :datasource, type: :string, repeatable: true
|
|
21
|
+
class_option :source, type: :string
|
|
22
|
+
class_option :api_key, type: :string
|
|
23
|
+
|
|
24
|
+
desc "Generates a new Strata project."
|
|
25
|
+
|
|
26
|
+
def validate_options
|
|
27
|
+
if options.key?(:source)
|
|
28
|
+
unless options.key?(:api_key)
|
|
29
|
+
raise Strata::CommandError,
|
|
30
|
+
"API key is required when using --source option. Use --api-key option."
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
unless @name && !@name.to_s.strip.empty?
|
|
34
|
+
raise Strata::CommandError,
|
|
35
|
+
"PROJECT_NAME is required when not using --source option."
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
13
39
|
|
|
14
|
-
|
|
40
|
+
def fetch_and_clone_if_available
|
|
41
|
+
return unless options.key?(:source)
|
|
15
42
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class_option :api_key, type: :string
|
|
43
|
+
project_info = fetch_project_info
|
|
44
|
+
@project_id = project_info["id"]
|
|
45
|
+
@project_info = project_info
|
|
20
46
|
|
|
21
|
-
|
|
47
|
+
return unless project_info["git_url"] && !project_info["git_url"].empty?
|
|
22
48
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
raise Strata::CommandError, "API key is required when using --source option. Use --api-key option." unless options.key?(:api_key)
|
|
26
|
-
else
|
|
27
|
-
raise Strata::CommandError, "PROJECT_NAME is required when not using --source option." unless @name && !@name.to_s.strip.empty?
|
|
49
|
+
@cloned_from_git = true
|
|
50
|
+
clone_project(project_info["git_url"])
|
|
28
51
|
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def fetch_and_clone_if_available
|
|
32
|
-
return unless options.key?(:source)
|
|
33
|
-
|
|
34
|
-
project_info = fetch_project_info
|
|
35
|
-
@project_id = project_info["id"]
|
|
36
|
-
@project_info = project_info
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
def create_project_structure
|
|
54
|
+
return if cloned_from_git?
|
|
39
55
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
56
|
+
empty_directory uid
|
|
57
|
+
empty_directory File.join(uid, "models")
|
|
58
|
+
empty_directory File.join(uid, "tests")
|
|
59
|
+
end
|
|
43
60
|
|
|
44
|
-
|
|
45
|
-
|
|
61
|
+
def create_strata_config_file
|
|
62
|
+
template "strata.yml", "#{uid}/.strata"
|
|
63
|
+
end
|
|
46
64
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
empty_directory File.join(uid, "tests")
|
|
50
|
-
end
|
|
65
|
+
def create_project_file
|
|
66
|
+
return if cloned_from_git?
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
end
|
|
68
|
+
template "project.yml", "#{uid}/project.yml"
|
|
69
|
+
end
|
|
55
70
|
|
|
56
|
-
|
|
57
|
-
|
|
71
|
+
def persist_project_id_if_needed
|
|
72
|
+
# Persist project_id after project.yml exists (either from clone or creation)
|
|
73
|
+
return unless @project_id
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
project_yml_path = File.join(uid, "project.yml")
|
|
76
|
+
return unless File.exist?(project_yml_path)
|
|
61
77
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return unless @project_id
|
|
78
|
+
persist_project_id_to_project_yml
|
|
79
|
+
end
|
|
65
80
|
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
def create_datasources_file
|
|
82
|
+
return if cloned_from_git?
|
|
68
83
|
|
|
69
|
-
|
|
70
|
-
end
|
|
84
|
+
template "datasources.yml", "#{uid}/datasources.yml"
|
|
71
85
|
|
|
72
|
-
|
|
73
|
-
return if cloned_from_git?
|
|
86
|
+
return unless options.key?(:datasource)
|
|
74
87
|
|
|
75
|
-
|
|
88
|
+
options[:datasource].each do |ds|
|
|
89
|
+
raise DWH::ConfigError, "Unsupported datasource #{ds}" unless DWH.adapter?(ds.to_sym)
|
|
76
90
|
|
|
77
|
-
|
|
91
|
+
Datasource.new([ds.downcase.strip], options.merge({"path" => uid})).invoke_all
|
|
92
|
+
end
|
|
93
|
+
end
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
def initialize_git
|
|
96
|
+
return if cloned_from_git?
|
|
81
97
|
|
|
82
|
-
|
|
98
|
+
inside uid do
|
|
99
|
+
run "git init", verbose: false, capture: true
|
|
100
|
+
create_file ".gitignore", ".strata\n"
|
|
101
|
+
end
|
|
83
102
|
end
|
|
84
|
-
end
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
104
|
+
def setup_datasource
|
|
105
|
+
return if cloned_from_git?
|
|
106
|
+
return if options.key?(:datasource) # Already specified via CLI option
|
|
107
|
+
|
|
108
|
+
say "\n", :white
|
|
109
|
+
say_status :setup, "Let's configure your first datasource", :cyan
|
|
88
110
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
111
|
+
# Change into the project directory and run the existing add command
|
|
112
|
+
inside(uid) do
|
|
113
|
+
require_relative "../sub_commands/datasource"
|
|
114
|
+
SubCommands::Datasource.new.add
|
|
115
|
+
end
|
|
92
116
|
end
|
|
93
|
-
end
|
|
94
117
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
118
|
+
def completion_message
|
|
119
|
+
say "\n✔ Strata project '#{uid}' is ready!", :green
|
|
120
|
+
say "\nNext steps:", :yellow
|
|
121
|
+
say " 1. cd #{uid}", :cyan
|
|
122
|
+
say " 2. strata datasource add # To add more datasources", :cyan
|
|
123
|
+
say " 3. strata create table # Start adding tables", :cyan
|
|
124
|
+
say "\n"
|
|
125
|
+
end
|
|
98
126
|
|
|
99
|
-
|
|
100
|
-
say_status :setup, "Let's configure your first datasource", :cyan
|
|
127
|
+
private
|
|
101
128
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
require_relative "../sub_commands/datasource"
|
|
105
|
-
SubCommands::Datasource.new.add
|
|
129
|
+
def cloned_from_git?
|
|
130
|
+
@cloned_from_git ||= false
|
|
106
131
|
end
|
|
107
|
-
end
|
|
108
132
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
say " 3. strata create table # Start adding tables", :cyan
|
|
115
|
-
say "\n"
|
|
116
|
-
end
|
|
133
|
+
def fetch_project_info
|
|
134
|
+
conn = Faraday.new(url: options[:source]) do |f|
|
|
135
|
+
f.request :authorization, "Bearer", options[:api_key]
|
|
136
|
+
f.response :json
|
|
137
|
+
end
|
|
117
138
|
|
|
118
|
-
|
|
139
|
+
response = with_connection_error_handling(options[:source]) do
|
|
140
|
+
conn.get
|
|
141
|
+
end
|
|
119
142
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
143
|
+
unless response.success?
|
|
144
|
+
raise Strata::CommandError,
|
|
145
|
+
"Failed to fetch project info from #{options[:source]}. Status: #{response.status}"
|
|
146
|
+
end
|
|
123
147
|
|
|
124
|
-
|
|
125
|
-
conn = Faraday.new(url: options[:source]) do |f|
|
|
126
|
-
f.request :authorization, "Bearer", options[:api_key]
|
|
127
|
-
f.response :json
|
|
148
|
+
response.body
|
|
128
149
|
end
|
|
129
150
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
151
|
+
def clone_project(git_url)
|
|
152
|
+
say_status :clone, "Cloning project from #{git_url}", :green
|
|
153
|
+
run "git clone #{git_url} #{uid}", verbose: false, capture: true
|
|
154
|
+
|
|
155
|
+
raise Strata::CommandError, "Failed to clone repository from #{git_url}" unless File.directory?(uid)
|
|
133
156
|
|
|
134
|
-
|
|
135
|
-
raise Strata::CommandError,
|
|
136
|
-
"Failed to fetch project info from #{options[:source]}. Status: #{response.status}"
|
|
157
|
+
say_status :success, "Project cloned successfully", :green
|
|
137
158
|
end
|
|
138
159
|
|
|
139
|
-
|
|
140
|
-
|
|
160
|
+
def persist_project_id_to_project_yml
|
|
161
|
+
return unless @project_id
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
project_yml_path = File.join(uid, "project.yml")
|
|
164
|
+
Helpers::ProjectHelper.persist_project_id_to_yml(@project_id, project_yml_path: project_yml_path)
|
|
165
|
+
end
|
|
145
166
|
|
|
146
|
-
|
|
167
|
+
def uid
|
|
168
|
+
@uid ||= if options.key?(:source) && @project_info
|
|
169
|
+
@project_info["uid"] || options[:source].split("/").last
|
|
170
|
+
else
|
|
171
|
+
Utils.url_safe_str(name)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
147
174
|
|
|
148
|
-
|
|
149
|
-
|
|
175
|
+
def name
|
|
176
|
+
# Use name from server if available (when using --source), otherwise use argument
|
|
177
|
+
return @project_info["name"] if options.key?(:source) && @project_info
|
|
150
178
|
|
|
151
|
-
|
|
152
|
-
|
|
179
|
+
@name
|
|
180
|
+
end
|
|
153
181
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
end
|
|
182
|
+
def server_url
|
|
183
|
+
return options[:source] if options.key?(:source)
|
|
157
184
|
|
|
158
|
-
|
|
159
|
-
@uid ||= if options.key?(:source) && @project_info
|
|
160
|
-
@project_info["uid"] || options[:source].split("/").last
|
|
161
|
-
else
|
|
162
|
-
Utils.url_safe_str(name)
|
|
185
|
+
BASE_SERVER_URL
|
|
163
186
|
end
|
|
164
|
-
end
|
|
165
187
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return @project_info["name"] if options.key?(:source) && @project_info
|
|
169
|
-
@name
|
|
170
|
-
end
|
|
188
|
+
def description
|
|
189
|
+
return @project_info["description"] if options.key?(:source) && @project_info
|
|
171
190
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
BASE_SERVER_URL
|
|
175
|
-
end
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
176
193
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
194
|
+
def production_branch
|
|
195
|
+
if options.key?(:source) && @project_info && @project_info["production_branch"]
|
|
196
|
+
return @project_info["production_branch"]
|
|
197
|
+
end
|
|
181
198
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
"main"
|
|
185
|
-
end
|
|
199
|
+
"main"
|
|
200
|
+
end
|
|
186
201
|
|
|
187
|
-
|
|
202
|
+
attr_reader :project_id
|
|
203
|
+
end
|
|
188
204
|
end
|
|
189
205
|
end
|
|
190
206
|
end
|
|
@@ -109,9 +109,7 @@ module Strata
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
synonyms = normalized[:synonyms]
|
|
112
|
-
if synonyms.is_a?(Array) && !synonyms.empty?
|
|
113
|
-
field_hash["synonyms"] = synonyms
|
|
114
|
-
end
|
|
112
|
+
field_hash["synonyms"] = synonyms if synonyms.is_a?(Array) && !synonyms.empty?
|
|
115
113
|
|
|
116
114
|
# Build expression with proper nested format
|
|
117
115
|
expr = normalized[:expression]
|
data/lib/strata/cli/guard.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "tty-prompt"
|
|
|
4
4
|
require_relative "datasource_helper"
|
|
5
5
|
require_relative "color_helper"
|
|
6
6
|
require_relative "prompts"
|
|
7
|
+
require_relative "../error_reporter"
|
|
7
8
|
|
|
8
9
|
module Strata
|
|
9
10
|
module CLI
|
|
@@ -23,18 +24,36 @@ module Strata
|
|
|
23
24
|
@datasource_key ||= resolve_datasource(prompt: prompt)
|
|
24
25
|
end
|
|
25
26
|
|
|
26
|
-
def
|
|
27
|
-
@
|
|
27
|
+
def table_fetch_result
|
|
28
|
+
@table_fetch_result ||= begin
|
|
28
29
|
tables = with_spinner("Fetching tables from #{datasource_key}...") { adapter.tables }
|
|
29
30
|
if tables.empty?
|
|
30
31
|
# Assuming Prompts and ColorHelper are available in the class or via module
|
|
31
32
|
say Prompts::MSG_NO_TABLES_FOUND % datasource_key, ColorHelper.warning
|
|
32
|
-
[]
|
|
33
|
+
{tables: [], failed: false}
|
|
34
|
+
else
|
|
35
|
+
{tables: tables, failed: false}
|
|
36
|
+
end
|
|
37
|
+
rescue => e
|
|
38
|
+
ErrorReporter.log_error(e, context: "create table: failed fetching tables for '#{datasource_key}'")
|
|
39
|
+
|
|
40
|
+
if ErrorReporter.connection_error?(e)
|
|
41
|
+
say "Could not connect to datasource '#{datasource_key}'.", ColorHelper.warning
|
|
33
42
|
else
|
|
34
|
-
tables
|
|
43
|
+
say "Could not fetch tables from datasource '#{datasource_key}'.", ColorHelper.warning
|
|
44
|
+
say "Reason: #{ErrorReporter.user_message_for(e)}", ColorHelper.warning
|
|
35
45
|
end
|
|
46
|
+
|
|
47
|
+
say "Hint: verify credentials/settings and run 'strata datasource test #{datasource_key}'.",
|
|
48
|
+
ColorHelper.info
|
|
49
|
+
say "Details logged to '#{ErrorReporter.log_relative_path}'.", ColorHelper.info
|
|
50
|
+
{tables: [], failed: true}
|
|
36
51
|
end
|
|
37
52
|
end
|
|
53
|
+
|
|
54
|
+
def all_tables
|
|
55
|
+
table_fetch_result[:tables]
|
|
56
|
+
end
|
|
38
57
|
end
|
|
39
58
|
end
|
|
40
59
|
end
|
|
@@ -105,6 +105,22 @@ module Strata
|
|
|
105
105
|
datasources[ds_key]
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
+
def resolve_datasource_value(value)
|
|
109
|
+
return nil if value.nil? || value.to_s.strip.empty?
|
|
110
|
+
|
|
111
|
+
normalized = value.to_s.downcase.strip
|
|
112
|
+
|
|
113
|
+
# Match by key first
|
|
114
|
+
return value if datasources.key?(normalized)
|
|
115
|
+
|
|
116
|
+
# Match by name (case-insensitive)
|
|
117
|
+
datasources.each do |key, config|
|
|
118
|
+
return key if config.is_a?(Hash) && config["name"].to_s.downcase.strip == normalized
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
nil
|
|
122
|
+
end
|
|
123
|
+
|
|
108
124
|
private
|
|
109
125
|
|
|
110
126
|
def validate_datasource(ds_key)
|
|
@@ -116,7 +132,7 @@ module Strata
|
|
|
116
132
|
end
|
|
117
133
|
|
|
118
134
|
def datasources
|
|
119
|
-
@
|
|
135
|
+
@datasources ||= begin
|
|
120
136
|
YAML.safe_load_file("datasources.yml", permitted_classes: [Date, Time], aliases: true) || {}
|
|
121
137
|
rescue Errno::ENOENT
|
|
122
138
|
{}
|
data/lib/strata/cli/main.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require_relative "generators/project"
|
|
2
4
|
require_relative "sub_commands/datasource"
|
|
3
5
|
require_relative "sub_commands/deploy"
|
|
@@ -33,7 +35,9 @@ module Strata
|
|
|
33
35
|
unless project_name || options[:source]
|
|
34
36
|
raise Strata::CommandError, "PROJECT_NAME is required when not using --source option."
|
|
35
37
|
end
|
|
36
|
-
|
|
38
|
+
|
|
39
|
+
say_status :started, "Creating #{project_name || "project from source"} - #{options[:datasource]}",
|
|
40
|
+
ColorHelper.info
|
|
37
41
|
invoke Generators::Project, [project_name], options
|
|
38
42
|
end
|
|
39
43
|
|
|
@@ -19,10 +19,10 @@ module Strata
|
|
|
19
19
|
include Terminal
|
|
20
20
|
include DatasourceHelper
|
|
21
21
|
|
|
22
|
-
REQUIRED_KEYS_FOR_TABLE_MODEL = %w[name physical_name fields datasource]
|
|
23
|
-
REQUIRED_KEYS_FOR_RELATIONSHIP_MODEL = ["datasource"]
|
|
24
|
-
REQUIRED_KEYS_FOR_RELATIONSHIP_DEFINITION = %w[left right sql cardinality]
|
|
25
|
-
RELATIONSHIP_CARDINALITIES = %w[one_to_one one_to_many many_to_one many_to_many]
|
|
22
|
+
REQUIRED_KEYS_FOR_TABLE_MODEL = %w[name physical_name fields datasource].freeze
|
|
23
|
+
REQUIRED_KEYS_FOR_RELATIONSHIP_MODEL = ["datasource"].freeze
|
|
24
|
+
REQUIRED_KEYS_FOR_RELATIONSHIP_DEFINITION = %w[left right sql cardinality].freeze
|
|
25
|
+
RELATIONSHIP_CARDINALITIES = %w[one_to_one one_to_many many_to_one many_to_many].freeze
|
|
26
26
|
|
|
27
27
|
# Set default command so `strata audit` still works as `strata audit all`
|
|
28
28
|
default_command :all
|
|
@@ -131,21 +131,35 @@ module Strata
|
|
|
131
131
|
end
|
|
132
132
|
|
|
133
133
|
def validate_structure(content, file, failures)
|
|
134
|
-
content.
|
|
134
|
+
content.each_key do |k|
|
|
135
135
|
failures << {file: file, message: "Top level key #{k} is not a string"} unless k.is_a?(String)
|
|
136
136
|
end
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
def validate_table_model(content, file, failures)
|
|
140
140
|
validate_required_keys(content, file, REQUIRED_KEYS_FOR_TABLE_MODEL, failures)
|
|
141
|
+
validate_datasource_reference(content, file, failures)
|
|
141
142
|
validate_table_fields(content["fields"], file, failures) if content.key?("fields")
|
|
142
143
|
end
|
|
143
144
|
|
|
144
145
|
def validate_relationship_model(content, file, failures)
|
|
145
146
|
validate_required_keys(content, file, REQUIRED_KEYS_FOR_RELATIONSHIP_MODEL, failures)
|
|
147
|
+
validate_datasource_reference(content, file, failures)
|
|
146
148
|
validate_relationship_definitions(content, file, failures)
|
|
147
149
|
end
|
|
148
150
|
|
|
151
|
+
def validate_datasource_reference(content, file, failures)
|
|
152
|
+
ds_value = content["datasource"]
|
|
153
|
+
return if ds_value.nil? || ds_value.to_s.strip.empty?
|
|
154
|
+
|
|
155
|
+
return if resolve_datasource_value(ds_value)
|
|
156
|
+
|
|
157
|
+
failures << {
|
|
158
|
+
file: file,
|
|
159
|
+
message: "Datasource '#{ds_value}' not found. Must match a key or name in datasources.yml"
|
|
160
|
+
}
|
|
161
|
+
end
|
|
162
|
+
|
|
149
163
|
def validate_required_keys(content, file, required_keys, failures)
|
|
150
164
|
missing = required_keys - content.keys
|
|
151
165
|
failures << {file: file, message: "Missing required keys: #{missing.join(", ")}"} if missing.any?
|
|
@@ -126,9 +126,15 @@ module Strata
|
|
|
126
126
|
end
|
|
127
127
|
|
|
128
128
|
def handle_table_creation_interactive
|
|
129
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
386
|
-
|
|
387
|
-
|
|
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)
|