strata-cli 0.1.0.beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +65 -0
- data/LICENSE +21 -0
- data/README.md +465 -0
- data/Rakefile +10 -0
- data/exe/strata +6 -0
- data/lib/strata/cli/ai/client.rb +63 -0
- data/lib/strata/cli/ai/configuration.rb +48 -0
- data/lib/strata/cli/ai/services/table_generator.rb +282 -0
- data/lib/strata/cli/api/client.rb +170 -0
- data/lib/strata/cli/api/connection_error_handler.rb +54 -0
- data/lib/strata/cli/configuration.rb +135 -0
- data/lib/strata/cli/credentials.rb +83 -0
- data/lib/strata/cli/descriptions/create/migration.txt +25 -0
- data/lib/strata/cli/descriptions/create/relation.txt +14 -0
- data/lib/strata/cli/descriptions/create/table.txt +23 -0
- data/lib/strata/cli/descriptions/datasource/add.txt +15 -0
- data/lib/strata/cli/descriptions/datasource/auth.txt +14 -0
- data/lib/strata/cli/descriptions/datasource/exec.txt +7 -0
- data/lib/strata/cli/descriptions/datasource/meta.txt +11 -0
- data/lib/strata/cli/descriptions/datasource/tables.txt +12 -0
- data/lib/strata/cli/descriptions/datasource/test.txt +8 -0
- data/lib/strata/cli/descriptions/deploy/deploy.txt +24 -0
- data/lib/strata/cli/descriptions/deploy/status.txt +9 -0
- data/lib/strata/cli/descriptions/init.txt +14 -0
- data/lib/strata/cli/generators/datasource.rb +83 -0
- data/lib/strata/cli/generators/group.rb +13 -0
- data/lib/strata/cli/generators/migration.rb +71 -0
- data/lib/strata/cli/generators/project.rb +190 -0
- data/lib/strata/cli/generators/relation.rb +64 -0
- data/lib/strata/cli/generators/table.rb +143 -0
- data/lib/strata/cli/generators/templates/adapters/athena.yml +53 -0
- data/lib/strata/cli/generators/templates/adapters/druid.yml +42 -0
- data/lib/strata/cli/generators/templates/adapters/duckdb.yml +36 -0
- data/lib/strata/cli/generators/templates/adapters/mysql.yml +45 -0
- data/lib/strata/cli/generators/templates/adapters/postgres.yml +48 -0
- data/lib/strata/cli/generators/templates/adapters/snowflake.yml +69 -0
- data/lib/strata/cli/generators/templates/adapters/sqlserver.yml +45 -0
- data/lib/strata/cli/generators/templates/adapters/trino.yml +56 -0
- data/lib/strata/cli/generators/templates/datasources.yml +4 -0
- data/lib/strata/cli/generators/templates/migration.rename.yml +15 -0
- data/lib/strata/cli/generators/templates/migration.swap.yml +13 -0
- data/lib/strata/cli/generators/templates/project.yml +36 -0
- data/lib/strata/cli/generators/templates/rel.domain.yml +43 -0
- data/lib/strata/cli/generators/templates/strata.yml +24 -0
- data/lib/strata/cli/generators/templates/table.table_name.yml +118 -0
- data/lib/strata/cli/generators/templates/test.yml +34 -0
- data/lib/strata/cli/generators/test.rb +48 -0
- data/lib/strata/cli/guard.rb +21 -0
- data/lib/strata/cli/helpers/color_helper.rb +103 -0
- data/lib/strata/cli/helpers/command_context.rb +41 -0
- data/lib/strata/cli/helpers/datasource_helper.rb +62 -0
- data/lib/strata/cli/helpers/description_helper.rb +18 -0
- data/lib/strata/cli/helpers/project_helper.rb +85 -0
- data/lib/strata/cli/helpers/prompts.rb +42 -0
- data/lib/strata/cli/helpers/table_filter.rb +48 -0
- data/lib/strata/cli/main.rb +71 -0
- data/lib/strata/cli/sub_commands/audit.rb +262 -0
- data/lib/strata/cli/sub_commands/create.rb +419 -0
- data/lib/strata/cli/sub_commands/datasource.rb +353 -0
- data/lib/strata/cli/sub_commands/deploy.rb +433 -0
- data/lib/strata/cli/sub_commands/project.rb +38 -0
- data/lib/strata/cli/sub_commands/table.rb +58 -0
- data/lib/strata/cli/terminal.rb +102 -0
- data/lib/strata/cli/ui/autocomplete.rb +93 -0
- data/lib/strata/cli/ui/field_editor.rb +215 -0
- data/lib/strata/cli/utils/archive.rb +137 -0
- data/lib/strata/cli/utils/deployment_monitor.rb +445 -0
- data/lib/strata/cli/utils/git.rb +253 -0
- data/lib/strata/cli/utils/import_manager.rb +190 -0
- data/lib/strata/cli/utils/test_reporter.rb +131 -0
- data/lib/strata/cli/utils/yaml_import_resolver.rb +91 -0
- data/lib/strata/cli/utils.rb +39 -0
- data/lib/strata/cli/version.rb +7 -0
- data/lib/strata/cli.rb +36 -0
- data/sig/strata/cli.rbs +6 -0
- metadata +306 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Strata
|
|
4
|
+
module CLI
|
|
5
|
+
# Shared module for table filtering and display name extraction
|
|
6
|
+
module TableFilter
|
|
7
|
+
# Extract display name from full table path
|
|
8
|
+
# e.g., "sales/analytics/customer" -> "customer"
|
|
9
|
+
# @param table_path [String] Full table path
|
|
10
|
+
# @return [String] Last element of the path
|
|
11
|
+
def self.display_name(table_path)
|
|
12
|
+
table_path.split("/").last || table_path
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Filter tables by pattern (case-insensitive)
|
|
16
|
+
# @param tables [Array<String>] List of table paths
|
|
17
|
+
# @param pattern [String] Search pattern
|
|
18
|
+
# @return [Array<String>] Filtered tables
|
|
19
|
+
def self.filter(tables, pattern)
|
|
20
|
+
return tables if pattern.nil? || pattern.empty?
|
|
21
|
+
|
|
22
|
+
regex = Regexp.new(pattern, Regexp::IGNORECASE)
|
|
23
|
+
tables.select { |table| table.match?(regex) || display_name(table).match?(regex) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Paginate array of items
|
|
27
|
+
# @param items [Array] Items to paginate
|
|
28
|
+
# @param page [Integer] Current page (1-indexed)
|
|
29
|
+
# @param per_page [Integer] Items per page
|
|
30
|
+
# @return [Array] Items for current page
|
|
31
|
+
def self.paginate(items, page: 1, per_page: 10)
|
|
32
|
+
start_idx = (page - 1) * per_page
|
|
33
|
+
end_idx = start_idx + per_page
|
|
34
|
+
items[start_idx...end_idx] || []
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get total pages for pagination
|
|
38
|
+
# @param items [Array] Items to paginate
|
|
39
|
+
# @param per_page [Integer] Items per page
|
|
40
|
+
# @return [Integer] Total number of pages
|
|
41
|
+
def self.total_pages(items, per_page: 10)
|
|
42
|
+
return 1 if items.empty?
|
|
43
|
+
|
|
44
|
+
(items.length.to_f / per_page).ceil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
require_relative "generators/project"
|
|
2
|
+
require_relative "sub_commands/datasource"
|
|
3
|
+
require_relative "sub_commands/deploy"
|
|
4
|
+
require_relative "sub_commands/project"
|
|
5
|
+
require_relative "sub_commands/table"
|
|
6
|
+
require_relative "sub_commands/create"
|
|
7
|
+
require_relative "sub_commands/audit"
|
|
8
|
+
require_relative "helpers/description_helper"
|
|
9
|
+
|
|
10
|
+
module Strata
|
|
11
|
+
module CLI
|
|
12
|
+
class Main < Thor
|
|
13
|
+
include Guard
|
|
14
|
+
extend Helpers::DescriptionHelper
|
|
15
|
+
|
|
16
|
+
def self.exit_on_failure?
|
|
17
|
+
true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "version", "Prints this version of strata-cli"
|
|
21
|
+
def version
|
|
22
|
+
say VERSION
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "init [PROJECT_NAME]", "Initializes a new Strata project. PROJECT_NAME is optional when using --source."
|
|
26
|
+
long_desc_from_file "init"
|
|
27
|
+
option :datasource, aliases: ["d"], type: :string, desc: "One of the supported data warehouse adapters.",
|
|
28
|
+
repeatable: true
|
|
29
|
+
option :source, aliases: ["s"], type: :string, desc: "URL of existing project"
|
|
30
|
+
option :api_key, aliases: ["a"], type: :string, desc: "Api Key. Required if initializing existing project."
|
|
31
|
+
|
|
32
|
+
def init(project_name = nil)
|
|
33
|
+
unless project_name || options[:source]
|
|
34
|
+
raise Strata::CommandError, "PROJECT_NAME is required when not using --source option."
|
|
35
|
+
end
|
|
36
|
+
say_status :started, "Creating #{project_name || "project from source"} - #{options[:datasource]}", ColorHelper.info
|
|
37
|
+
invoke Generators::Project, [project_name], options
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "adapters", "Lists supported data warehouse adapters"
|
|
41
|
+
def adapters
|
|
42
|
+
out = " SUPPORTED ADAPTERS: \n\t#{DWH.adapters.keys.join("\n\t")}"
|
|
43
|
+
say out, ColorHelper.info
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "datasource", "Manage project datasources"
|
|
47
|
+
subcommand "datasource", SubCommands::Datasource
|
|
48
|
+
|
|
49
|
+
desc "create", "Create semantic models (tables, relations)"
|
|
50
|
+
subcommand "create", SubCommands::Create
|
|
51
|
+
|
|
52
|
+
desc "table", "Manage semantic tables"
|
|
53
|
+
subcommand "table", SubCommands::Table
|
|
54
|
+
|
|
55
|
+
desc "audit", "Audit project configuration and models"
|
|
56
|
+
subcommand "audit", SubCommands::Audit
|
|
57
|
+
|
|
58
|
+
desc "deploy", "Deploy project to Strata server"
|
|
59
|
+
subcommand "deploy", SubCommands::Deploy
|
|
60
|
+
|
|
61
|
+
desc "project", "Manage project configuration"
|
|
62
|
+
subcommand "project", SubCommands::Project
|
|
63
|
+
|
|
64
|
+
# Creating aliases
|
|
65
|
+
map "t" => "table"
|
|
66
|
+
map "tbl" => "table"
|
|
67
|
+
map "ds" => "datasource"
|
|
68
|
+
map "a" => "audit"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../guard"
|
|
4
|
+
require_relative "../terminal"
|
|
5
|
+
require_relative "../credentials"
|
|
6
|
+
require_relative "../helpers/color_helper"
|
|
7
|
+
require_relative "../helpers/datasource_helper"
|
|
8
|
+
require_relative "../utils/yaml_import_resolver"
|
|
9
|
+
require_relative "../utils/import_manager"
|
|
10
|
+
require "yaml"
|
|
11
|
+
require "pathname"
|
|
12
|
+
|
|
13
|
+
module Strata
|
|
14
|
+
module CLI
|
|
15
|
+
module SubCommands
|
|
16
|
+
class Audit < Thor
|
|
17
|
+
include Thor::Actions
|
|
18
|
+
include Guard
|
|
19
|
+
include Terminal
|
|
20
|
+
include DatasourceHelper
|
|
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]
|
|
26
|
+
|
|
27
|
+
# Set default command so `strata audit` still works as `strata audit all`
|
|
28
|
+
default_command :all
|
|
29
|
+
|
|
30
|
+
desc "all", "Run all audit checks"
|
|
31
|
+
def all
|
|
32
|
+
results = {}
|
|
33
|
+
results[:yaml] = run_check("Checking YAML syntax") { audit_yaml_syntax }
|
|
34
|
+
results[:models] = run_check("Checking model definitions") { audit_models }
|
|
35
|
+
results[:connections] = run_check("Checking data source connections") { audit_connections }
|
|
36
|
+
|
|
37
|
+
report_results(results)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "syntax", "Check YAML syntax of configuration files"
|
|
41
|
+
def syntax
|
|
42
|
+
results = {yaml: run_check("Checking YAML syntax") { audit_yaml_syntax }}
|
|
43
|
+
report_results(results)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "models", "Check model definitions and schema structure"
|
|
47
|
+
def models
|
|
48
|
+
results = {models: run_check("Checking model definitions") { audit_models }}
|
|
49
|
+
report_results(results)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
desc "connections", "Check data source connections"
|
|
53
|
+
def connections
|
|
54
|
+
results = {connections: run_check("Checking data source connections") { audit_connections }}
|
|
55
|
+
report_results(results)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def report_results(results)
|
|
61
|
+
if results.values.all?
|
|
62
|
+
# All checks passed - no need for additional message, spinners already show success
|
|
63
|
+
else
|
|
64
|
+
say "\n Some checks failed.", ColorHelper.error
|
|
65
|
+
exit(1)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def run_check(message)
|
|
70
|
+
failures = []
|
|
71
|
+
with_spinner(message) do
|
|
72
|
+
failures = yield
|
|
73
|
+
raise StandardError, "Check failed" if failures.any?
|
|
74
|
+
end
|
|
75
|
+
true
|
|
76
|
+
rescue
|
|
77
|
+
failures.each do |f|
|
|
78
|
+
msg = f.is_a?(Hash) ? "#{f[:file]}: #{f[:message]}" : f.to_s
|
|
79
|
+
say " ✖ #{msg}", ColorHelper.error
|
|
80
|
+
end
|
|
81
|
+
false
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def audit_yaml_syntax
|
|
85
|
+
files = Dir.glob("models/**/*.yml") + ["datasources.yml"].select { |f| File.exist?(f) }
|
|
86
|
+
|
|
87
|
+
failures = []
|
|
88
|
+
files.each do |file|
|
|
89
|
+
YAML.safe_load_file(file, permitted_classes: [Date, Time], aliases: true)
|
|
90
|
+
rescue Psych::SyntaxError => e
|
|
91
|
+
failures << {file: file, message: e.message}
|
|
92
|
+
rescue => e
|
|
93
|
+
failures << {file: file, message: e.message}
|
|
94
|
+
end
|
|
95
|
+
failures
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def audit_models
|
|
99
|
+
failures = []
|
|
100
|
+
Dir.glob("models/**/*.yml").each do |file|
|
|
101
|
+
audit_model_file(file, failures)
|
|
102
|
+
audit_imports(file, failures)
|
|
103
|
+
end
|
|
104
|
+
failures
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def audit_model_file(file, failures)
|
|
108
|
+
content = YAML.safe_load_file(file, permitted_classes: [Date, Time], aliases: true) || {}
|
|
109
|
+
return unless content.is_a?(Hash)
|
|
110
|
+
|
|
111
|
+
validate_structure(content, file, failures)
|
|
112
|
+
|
|
113
|
+
case model_type(file)
|
|
114
|
+
when :table
|
|
115
|
+
validate_table_model(content, file, failures)
|
|
116
|
+
when :relationship
|
|
117
|
+
validate_relationship_model(content, file, failures)
|
|
118
|
+
end
|
|
119
|
+
rescue
|
|
120
|
+
# Ignore errors here, they are handled in audit_yaml_syntax if needed
|
|
121
|
+
# or simply skipped if the file is unreadable
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def model_type(file)
|
|
125
|
+
filename = File.basename(file)
|
|
126
|
+
if filename.start_with?("tbl.")
|
|
127
|
+
:table
|
|
128
|
+
elsif filename.start_with?("rel.")
|
|
129
|
+
:relationship
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def validate_structure(content, file, failures)
|
|
134
|
+
content.keys.each do |k|
|
|
135
|
+
failures << {file: file, message: "Top level key #{k} is not a string"} unless k.is_a?(String)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def validate_table_model(content, file, failures)
|
|
140
|
+
validate_required_keys(content, file, REQUIRED_KEYS_FOR_TABLE_MODEL, failures)
|
|
141
|
+
validate_table_fields(content["fields"], file, failures) if content.key?("fields")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def validate_relationship_model(content, file, failures)
|
|
145
|
+
validate_required_keys(content, file, REQUIRED_KEYS_FOR_RELATIONSHIP_MODEL, failures)
|
|
146
|
+
validate_relationship_definitions(content, file, failures)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def validate_required_keys(content, file, required_keys, failures)
|
|
150
|
+
missing = required_keys - content.keys
|
|
151
|
+
failures << {file: file, message: "Missing required keys: #{missing.join(", ")}"} if missing.any?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def validate_table_fields(fields, file, failures)
|
|
155
|
+
unless fields.is_a?(Array)
|
|
156
|
+
failures << {file: file, message: "'fields' must be an array"}
|
|
157
|
+
return
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
fields.each_with_index do |field, idx|
|
|
161
|
+
unless field.is_a?(Hash) && field.key?("name")
|
|
162
|
+
failures << {file: file, message: "Field at index #{idx} missing 'name'"}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def validate_relationship_definitions(content, file, failures)
|
|
168
|
+
content.each do |key, definition|
|
|
169
|
+
next if key == "datasource"
|
|
170
|
+
|
|
171
|
+
unless definition.is_a?(Hash)
|
|
172
|
+
failures << {file: file, message: "Relationship '#{key}' is not a hash definition"}
|
|
173
|
+
next
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
validate_relationship_properties(key, definition, file, failures)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def validate_relationship_properties(name, definition, file, failures)
|
|
181
|
+
REQUIRED_KEYS_FOR_RELATIONSHIP_DEFINITION.each do |req|
|
|
182
|
+
failures << {file: file, message: "Relationship '#{name}' missing '#{req}'"} unless definition.key?(req)
|
|
183
|
+
end
|
|
184
|
+
validate_cardinality(name, definition["cardinality"], file, failures) if definition.key?("cardinality")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def validate_cardinality(name, cardinality, file, failures)
|
|
188
|
+
return if RELATIONSHIP_CARDINALITIES.include?(cardinality)
|
|
189
|
+
|
|
190
|
+
failures << {file: file, message: "Relationship '#{name}' has invalid cardinality '#{cardinality}'"}
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def audit_connections
|
|
194
|
+
failures = []
|
|
195
|
+
|
|
196
|
+
unless File.exist?("datasources.yml")
|
|
197
|
+
failures << {file: "datasources.yml", message: "File not found"}
|
|
198
|
+
return failures
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
if datasources.empty?
|
|
202
|
+
failures << {file: "datasources.yml",
|
|
203
|
+
message: "No datasources found. Run 'strata datasource add' to configure one."}
|
|
204
|
+
return failures
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
datasources.each_key do |ds_key|
|
|
208
|
+
adapter = create_adapter(ds_key)
|
|
209
|
+
adapter.test_connection(raise_exception: true)
|
|
210
|
+
rescue => e
|
|
211
|
+
failures << {
|
|
212
|
+
file: "datasources.yml",
|
|
213
|
+
message: "Connection failed for '#{ds_key}': #{e.message}"
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
failures
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def audit_imports(file, failures)
|
|
221
|
+
content = YAML.safe_load_file(file, permitted_classes: [Date, Time], aliases: true) || {}
|
|
222
|
+
return unless content.is_a?(Hash) && content["imports"]
|
|
223
|
+
|
|
224
|
+
project_path = Dir.pwd
|
|
225
|
+
base_dir = File.dirname(file)
|
|
226
|
+
|
|
227
|
+
content["imports"].each do |import_path|
|
|
228
|
+
# Validate import path is relative (will raise InvalidImportPathError if absolute)
|
|
229
|
+
Utils::ImportManager.validate_import_path(import_path)
|
|
230
|
+
|
|
231
|
+
# Validate that relative path resolves to an existing file
|
|
232
|
+
resolved_path = File.expand_path(import_path, base_dir)
|
|
233
|
+
unless File.exist?(resolved_path)
|
|
234
|
+
failures << {file: file, message: "Import file not found: #{import_path}"}
|
|
235
|
+
next
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
resolved_path = Utils::ImportManager.resolve_with_fallback(import_path, base_dir, project_path)
|
|
239
|
+
imported_content = YAML.safe_load_file(resolved_path, permitted_classes: [Date, Time], aliases: true) || {}
|
|
240
|
+
|
|
241
|
+
unless imported_content.is_a?(Hash)
|
|
242
|
+
failures << {file: file, message: "Imported file '#{import_path}' does not contain valid YAML hash"}
|
|
243
|
+
next
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
if model_type(file) == :table && imported_content["fields"]
|
|
247
|
+
validate_table_fields(imported_content["fields"], file, failures)
|
|
248
|
+
end
|
|
249
|
+
rescue Strata::InvalidImportPathError => e
|
|
250
|
+
failures << {file: file, message: e.message}
|
|
251
|
+
rescue Strata::MissingImportError
|
|
252
|
+
failures << {file: file, message: "Import file not found: #{import_path}"}
|
|
253
|
+
rescue Strata::CircularImportError => e
|
|
254
|
+
failures << {file: file, message: "Circular import detected: #{e.message}"}
|
|
255
|
+
rescue => e
|
|
256
|
+
failures << {file: file, message: "Error processing import '#{import_path}': #{e.message}"}
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|