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.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGELOG.md +11 -0
- data/README.md +3 -2
- data/lib/strata/cli/ai/services/table_generator.rb +8 -4
- 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 +4 -3
- 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 +3 -0
- data/lib/strata/cli/generators/templates/table.table_name.yml +6 -0
- 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 +34 -1
- data/lib/strata/cli/helpers/description_helper.rb +2 -0
- data/lib/strata/cli/helpers/project_helper.rb +28 -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 +11 -2
- data/lib/strata/cli/sub_commands/deploy.rb +79 -36
- 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 +6 -0
- data/lib/strata/cli/utils/archive.rb +1 -3
- data/lib/strata/cli/utils/deployment_monitor.rb +27 -25
- 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 +30 -28
|
@@ -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
|
|
@@ -108,6 +108,9 @@ module Strata
|
|
|
108
108
|
"data_type" => normalized[:data_type]
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
synonyms = normalized[:synonyms]
|
|
112
|
+
field_hash["synonyms"] = synonyms if synonyms.is_a?(Array) && !synonyms.empty?
|
|
113
|
+
|
|
111
114
|
# Build expression with proper nested format
|
|
112
115
|
expr = normalized[:expression]
|
|
113
116
|
field_hash["expression"] = {
|
|
@@ -96,6 +96,12 @@ fields:
|
|
|
96
96
|
# array: true|false (optional)
|
|
97
97
|
# sql: my_field_column (Required)
|
|
98
98
|
#
|
|
99
|
+
# # Optional: Alternative names for this field. Helps AI and search
|
|
100
|
+
# # find this field when users refer to it by different names.
|
|
101
|
+
# synonyms:
|
|
102
|
+
# - alt name one
|
|
103
|
+
# - alt name two
|
|
104
|
+
#
|
|
99
105
|
# # Optional: Exclude certain dimnesions from the group by/filter
|
|
100
106
|
# exclusion_type: exclude|exclude_all_except|exclude_all
|
|
101
107
|
# exclusions: # (Required when exclusion type is set and isnt exclude_all)
|
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
|
|
@@ -42,9 +42,26 @@ module Strata
|
|
|
42
42
|
config = ds_config(ds_key).merge(Credentials.fetch(ds_key))
|
|
43
43
|
adapter_sym = config["adapter"].to_sym
|
|
44
44
|
ensure_adapter_driver_gems!(adapter_sym)
|
|
45
|
+
|
|
46
|
+
# CLI only performs read operations (test, tables, metadata).
|
|
47
|
+
# Use read-only mode for file-based databases to avoid lock conflicts.
|
|
48
|
+
apply_readonly_mode(adapter_sym, config)
|
|
49
|
+
|
|
45
50
|
DWH.create(adapter_sym, config)
|
|
46
51
|
end
|
|
47
52
|
|
|
53
|
+
def apply_readonly_mode(adapter_sym, config)
|
|
54
|
+
case adapter_sym
|
|
55
|
+
when :duckdb
|
|
56
|
+
config["duck_config"] ||= {}
|
|
57
|
+
config["duck_config"]["access_mode"] = "READ_ONLY"
|
|
58
|
+
when :sqlite
|
|
59
|
+
config["readonly"] = true
|
|
60
|
+
end
|
|
61
|
+
# Client-server databases (postgres, mysql, snowflake, etc.)
|
|
62
|
+
# don't need special handling - no file lock issues
|
|
63
|
+
end
|
|
64
|
+
|
|
48
65
|
def ensure_adapter_driver_gems!(adapter_sym)
|
|
49
66
|
required = ADAPTER_DRIVER_GEMS.fetch(adapter_sym, [])
|
|
50
67
|
return if required.empty?
|
|
@@ -88,6 +105,22 @@ module Strata
|
|
|
88
105
|
datasources[ds_key]
|
|
89
106
|
end
|
|
90
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
|
+
|
|
91
124
|
private
|
|
92
125
|
|
|
93
126
|
def validate_datasource(ds_key)
|
|
@@ -99,7 +132,7 @@ module Strata
|
|
|
99
132
|
end
|
|
100
133
|
|
|
101
134
|
def datasources
|
|
102
|
-
@
|
|
135
|
+
@datasources ||= begin
|
|
103
136
|
YAML.safe_load_file("datasources.yml", permitted_classes: [Date, Time], aliases: true) || {}
|
|
104
137
|
rescue Errno::ENOENT
|
|
105
138
|
{}
|
|
@@ -46,6 +46,34 @@ module Strata
|
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
+
# Persists server URL to project.yml (updates existing or appends)
|
|
50
|
+
def persist_server_to_project_yml(server, project_yml_path: "project.yml")
|
|
51
|
+
return false unless server && !server.to_s.strip.empty?
|
|
52
|
+
return false unless File.exist?(project_yml_path)
|
|
53
|
+
|
|
54
|
+
project_yml_content = File.read(project_yml_path)
|
|
55
|
+
|
|
56
|
+
begin
|
|
57
|
+
if /^server:\s*.+$/m.match?(project_yml_content)
|
|
58
|
+
updated_content = project_yml_content.gsub(/^(\s*)server:\s*.+$/, "\\1server: #{server}")
|
|
59
|
+
File.write(project_yml_path, updated_content)
|
|
60
|
+
else
|
|
61
|
+
File.open(project_yml_path, "a") do |f|
|
|
62
|
+
f.puts "\n" unless project_yml_content.end_with?("\n")
|
|
63
|
+
f.puts "server: #{server}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
true
|
|
68
|
+
rescue Errno::EACCES => e
|
|
69
|
+
raise Strata::CommandError, "Permission denied writing to #{project_yml_path}: #{e.message}"
|
|
70
|
+
rescue Errno::ENOSPC => e
|
|
71
|
+
raise Strata::CommandError, "Disk full: #{e.message}"
|
|
72
|
+
rescue => e
|
|
73
|
+
raise Strata::CommandError, "Failed to write to #{project_yml_path}: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
49
77
|
# Persists git URL to project.yml file if missing
|
|
50
78
|
def persist_git_url_if_missing(project_yml_path: "project.yml")
|
|
51
79
|
return false unless File.exist?(project_yml_path)
|
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?
|