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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/.standard.yml +3 -0
  3. data/CHANGELOG.md +5 -0
  4. data/CLAUDE.md +65 -0
  5. data/LICENSE +21 -0
  6. data/README.md +465 -0
  7. data/Rakefile +10 -0
  8. data/exe/strata +6 -0
  9. data/lib/strata/cli/ai/client.rb +63 -0
  10. data/lib/strata/cli/ai/configuration.rb +48 -0
  11. data/lib/strata/cli/ai/services/table_generator.rb +282 -0
  12. data/lib/strata/cli/api/client.rb +170 -0
  13. data/lib/strata/cli/api/connection_error_handler.rb +54 -0
  14. data/lib/strata/cli/configuration.rb +135 -0
  15. data/lib/strata/cli/credentials.rb +83 -0
  16. data/lib/strata/cli/descriptions/create/migration.txt +25 -0
  17. data/lib/strata/cli/descriptions/create/relation.txt +14 -0
  18. data/lib/strata/cli/descriptions/create/table.txt +23 -0
  19. data/lib/strata/cli/descriptions/datasource/add.txt +15 -0
  20. data/lib/strata/cli/descriptions/datasource/auth.txt +14 -0
  21. data/lib/strata/cli/descriptions/datasource/exec.txt +7 -0
  22. data/lib/strata/cli/descriptions/datasource/meta.txt +11 -0
  23. data/lib/strata/cli/descriptions/datasource/tables.txt +12 -0
  24. data/lib/strata/cli/descriptions/datasource/test.txt +8 -0
  25. data/lib/strata/cli/descriptions/deploy/deploy.txt +24 -0
  26. data/lib/strata/cli/descriptions/deploy/status.txt +9 -0
  27. data/lib/strata/cli/descriptions/init.txt +14 -0
  28. data/lib/strata/cli/generators/datasource.rb +83 -0
  29. data/lib/strata/cli/generators/group.rb +13 -0
  30. data/lib/strata/cli/generators/migration.rb +71 -0
  31. data/lib/strata/cli/generators/project.rb +190 -0
  32. data/lib/strata/cli/generators/relation.rb +64 -0
  33. data/lib/strata/cli/generators/table.rb +143 -0
  34. data/lib/strata/cli/generators/templates/adapters/athena.yml +53 -0
  35. data/lib/strata/cli/generators/templates/adapters/druid.yml +42 -0
  36. data/lib/strata/cli/generators/templates/adapters/duckdb.yml +36 -0
  37. data/lib/strata/cli/generators/templates/adapters/mysql.yml +45 -0
  38. data/lib/strata/cli/generators/templates/adapters/postgres.yml +48 -0
  39. data/lib/strata/cli/generators/templates/adapters/snowflake.yml +69 -0
  40. data/lib/strata/cli/generators/templates/adapters/sqlserver.yml +45 -0
  41. data/lib/strata/cli/generators/templates/adapters/trino.yml +56 -0
  42. data/lib/strata/cli/generators/templates/datasources.yml +4 -0
  43. data/lib/strata/cli/generators/templates/migration.rename.yml +15 -0
  44. data/lib/strata/cli/generators/templates/migration.swap.yml +13 -0
  45. data/lib/strata/cli/generators/templates/project.yml +36 -0
  46. data/lib/strata/cli/generators/templates/rel.domain.yml +43 -0
  47. data/lib/strata/cli/generators/templates/strata.yml +24 -0
  48. data/lib/strata/cli/generators/templates/table.table_name.yml +118 -0
  49. data/lib/strata/cli/generators/templates/test.yml +34 -0
  50. data/lib/strata/cli/generators/test.rb +48 -0
  51. data/lib/strata/cli/guard.rb +21 -0
  52. data/lib/strata/cli/helpers/color_helper.rb +103 -0
  53. data/lib/strata/cli/helpers/command_context.rb +41 -0
  54. data/lib/strata/cli/helpers/datasource_helper.rb +62 -0
  55. data/lib/strata/cli/helpers/description_helper.rb +18 -0
  56. data/lib/strata/cli/helpers/project_helper.rb +85 -0
  57. data/lib/strata/cli/helpers/prompts.rb +42 -0
  58. data/lib/strata/cli/helpers/table_filter.rb +48 -0
  59. data/lib/strata/cli/main.rb +71 -0
  60. data/lib/strata/cli/sub_commands/audit.rb +262 -0
  61. data/lib/strata/cli/sub_commands/create.rb +419 -0
  62. data/lib/strata/cli/sub_commands/datasource.rb +353 -0
  63. data/lib/strata/cli/sub_commands/deploy.rb +433 -0
  64. data/lib/strata/cli/sub_commands/project.rb +38 -0
  65. data/lib/strata/cli/sub_commands/table.rb +58 -0
  66. data/lib/strata/cli/terminal.rb +102 -0
  67. data/lib/strata/cli/ui/autocomplete.rb +93 -0
  68. data/lib/strata/cli/ui/field_editor.rb +215 -0
  69. data/lib/strata/cli/utils/archive.rb +137 -0
  70. data/lib/strata/cli/utils/deployment_monitor.rb +445 -0
  71. data/lib/strata/cli/utils/git.rb +253 -0
  72. data/lib/strata/cli/utils/import_manager.rb +190 -0
  73. data/lib/strata/cli/utils/test_reporter.rb +131 -0
  74. data/lib/strata/cli/utils/yaml_import_resolver.rb +91 -0
  75. data/lib/strata/cli/utils.rb +39 -0
  76. data/lib/strata/cli/version.rb +7 -0
  77. data/lib/strata/cli.rb +36 -0
  78. data/sig/strata/cli.rbs +6 -0
  79. metadata +306 -0
@@ -0,0 +1,83 @@
1
+ require "yaml"
2
+ require "tty-prompt"
3
+
4
+ module Strata
5
+ module CLI
6
+ class Credentials
7
+ include Thor::Shell
8
+
9
+ # Retrieve existing credentials for the
10
+ # given ds_key or empty hash.
11
+ def self.fetch(ds_key)
12
+ CLI.config[ds_key] || {}
13
+ end
14
+
15
+ attr_reader :adapter, :credentials
16
+
17
+ def initialize(adapter)
18
+ @adapter = adapter.downcase.strip
19
+ @prompt = TTY::Prompt.new
20
+ end
21
+
22
+ def required?
23
+ adapter != "duckdb"
24
+ end
25
+
26
+ def collect
27
+ credentials = {}
28
+
29
+ case adapter
30
+ when "snowflake"
31
+ auth_mode = @prompt.select("Authentication mode:", %w[pat kp oauth], default: "pat")
32
+ credentials["auth_mode"] = auth_mode
33
+
34
+ case auth_mode
35
+ when "pat"
36
+ credentials["personal_access_token"] = @prompt.ask("Enter Personal Access Token:")
37
+ when "kp"
38
+ credentials["username"] = @prompt.ask("Enter Username:")
39
+ credentials["private_key"] = @prompt.ask("Enter Private Key Absolute Path:")
40
+ when "oauth"
41
+ credentials["oauth_client_id"] = @prompt.ask("OAuth Client ID:")
42
+ credentials["oauth_client_secret"] = @prompt.ask("OAuth Client Secret:")
43
+ credentials["oauth_redirect_uri"] = @prompt.ask("OAuth Redirect URI:", default: "https://localhost:3420/callback")
44
+ oauth_scope = @prompt.ask("OAuth Scope (optional):")
45
+ credentials["oauth_scope"] = oauth_scope unless oauth_scope.empty?
46
+ end
47
+ when "athena"
48
+ credentials["access_key_id"] = @prompt.ask("AWS Access Key ID:")
49
+ credentials["secret_access_key"] = @prompt.ask("AWS Secret Access Key:")
50
+ else
51
+ if required?
52
+ unless %w[postgres mysql trino sqlserver].include?(adapter)
53
+ credentials["username"] = @prompt.ask("Enter Username:")
54
+ end
55
+ credentials["password"] = @prompt.mask("Enter Password:")
56
+ end
57
+ end
58
+
59
+ @credentials = credentials
60
+ end
61
+
62
+ def collected?
63
+ !@credentials.keys.empty?
64
+ end
65
+
66
+ def write_local(ds_key, context)
67
+ context.gsub_file Configuration::STRATA_CONFIG_FILE, Utils.yaml_block_matcher(ds_key), ""
68
+ creds = "#{ds_key}:"
69
+ credentials.each do |key, val|
70
+ creds << "\n #{key}: #{val}" unless val.nil? || val.empty?
71
+ end
72
+
73
+ context.append_to_file Configuration::STRATA_CONFIG_FILE, creds
74
+
75
+ # Set restrictive permissions (read/write for owner only)
76
+ strata_file = Configuration::STRATA_CONFIG_FILE
77
+ File.chmod(0o600, strata_file) if File.exist?(strata_file)
78
+ rescue Errno::EACCES => e
79
+ raise Strata::CommandError, "Permission denied setting permissions on #{strata_file}: #{e.message}"
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,25 @@
1
+ Create migration files for renaming or swapping entities in your Strata project.
2
+
3
+ Migration subcommands:
4
+ rename Create a migration to rename an entity (dimension, measure, table, datasource)
5
+ swap Create a migration to swap entity references (dimension, measure, table)
6
+
7
+ Options:
8
+ -e, --entity TYPE Entity type: dimension, measure, table, or datasource (required)
9
+ -f, --from NAME Current/source entity name (required)
10
+ -t, --to NAME New/target entity name (required)
11
+
12
+ Examples:
13
+ # Rename a dimension
14
+ strata create migration rename -e dimension -f old_name -t new_name
15
+
16
+ # Rename a table
17
+ strata create migration rename -e table -f old_table -t new_table
18
+
19
+ # Swap two measures
20
+ strata create migration swap -e measure -f measure_a -t measure_b
21
+
22
+ # Rename a datasource
23
+ strata create migration rename -e datasource -f old_ds -t new_ds
24
+
25
+ Note: Swap operation does not support 'datasource' entity type.
@@ -0,0 +1,14 @@
1
+ Create a relation YAML file for defining joins between tables.
2
+
3
+ RELATION_PATH is the path where the relation file will be created.
4
+ If RELATION_PATH contains a /, the file will be created at models/<RELATION_PATH>/rel.<last_part>.yml
5
+ If RELATION_PATH has no /, the file will be created at models/rel.<RELATION_PATH>.yml
6
+ The relation name is derived from the last part of the path.
7
+
8
+ Examples:
9
+ strata create relation customer/orders # Creates models/customer/rel.orders.yml
10
+ strata create relation customer # Creates models/rel.customer.yml
11
+
12
+ This will create a template relation file with examples and comments.
13
+ You can then edit it to define your joins.
14
+
@@ -0,0 +1,23 @@
1
+ Create a semantic table model from a datasource table.
2
+
3
+ TABLE_PATH can be:
4
+ - Simple: call_center
5
+ - Nested: games/event_details
6
+ - With schema: contact/dse.call_center_d
7
+ - Deep nested: contact/help_center/visits
8
+
9
+ Examples:
10
+ strata create table call_center
11
+ strata create table games/event_details
12
+ strata create table contact/dse.call_center_d
13
+ strata create table contact/help_center/visits
14
+
15
+ If no argument is provided, you'll be prompted to search and select a table.
16
+
17
+ The command will:
18
+ 1. Check if the table exists in the datasource
19
+ 2. Fetch column metadata from the database
20
+ 3. Analyze columns with AI to suggest field definitions
21
+ 4. Allow you to review and edit fields interactively
22
+ 5. Generate the model YAML file at models/<TABLE_PATH>/tbl.<table_name>.yml
23
+
@@ -0,0 +1,15 @@
1
+ Add a new datasource interactively. This command will guide you through:
2
+
3
+ 1. Selecting a data warehouse adapter (PostgreSQL, MySQL, Snowflake, etc.)
4
+ 2. Configuring connection settings (host, port, database, etc.)
5
+ 3. Setting up authentication credentials
6
+ 4. Optionally configuring AI features for table generation
7
+
8
+ ADAPTER is optional - if not provided, you'll be prompted to select one.
9
+
10
+ Examples:
11
+ strata datasource add # Interactive mode - select adapter
12
+ strata datasource add postgres # Directly add PostgreSQL datasource
13
+ strata datasource add snowflake # Directly add Snowflake datasource
14
+
15
+ Supported adapters: postgres, mysql, sqlserver, snowflake, athena, trino, duckdb, druid
@@ -0,0 +1,14 @@
1
+ Example to set local credentials for a datasource with key games_dwh:
2
+
3
+ strata ds auth games_dwh
4
+
5
+ Example to set remote credentials for the same datasource:
6
+
7
+ strata ds auth games_dwh -r
8
+
9
+ Local credentials are saved in the projects .strata file. This will should not
10
+ be checked into git.
11
+
12
+ Remote credentials will securely send the credentials to the Strata server. This is
13
+ not requires for some modes like OAuth. In that case each user will be prompted to
14
+ go through the OAuth flow. Not all adapters support OAuth.
@@ -0,0 +1,7 @@
1
+ Example: run a single query inline on csops_db
2
+
3
+ strata ds exec csops_db -q "select * from customer limit 10;"
4
+
5
+ Example: run queries from a file
6
+
7
+ strata ds exec csops_db -f path/to/my/file.sql
@@ -0,0 +1,11 @@
1
+ Show the schema/structure of a specific table in the datasource.
2
+ Displays column names, data types, and other metadata.
3
+
4
+ Options:
5
+ -c, --catalog CATALOG Override catalog from datasource config
6
+ -s, --schema SCHEMA Override schema from datasource config
7
+
8
+ Examples:
9
+ strata datasource meta my_db customers # Show customers table structure
10
+ strata datasource meta my_db dbo.orders # Show orders table in dbo schema
11
+ strata datasource meta my_db sales.customers -s sales # Explicitly specify schema
@@ -0,0 +1,12 @@
1
+ List all tables available in the specified datasource. If DS_KEY is not provided,
2
+ you'll be prompted to select from available datasources.
3
+
4
+ Options:
5
+ -p, --pattern PATTERN Filter tables by regex pattern
6
+ -c, --catalog CATALOG Override catalog from datasource config
7
+ -s, --schema SCHEMA Override schema from datasource config
8
+
9
+ Examples:
10
+ strata datasource tables my_db # List all tables
11
+ strata datasource tables my_db -p user # List tables matching "user"
12
+ strata datasource tables my_db -s dbo # List tables in "dbo" schema
@@ -0,0 +1,8 @@
1
+ Test the connection to a datasource. This verifies that:
2
+ - The datasource configuration is valid
3
+ - Credentials are correct
4
+ - The connection can be established
5
+
6
+ Examples:
7
+ strata datasource test my_db # Test connection to my_db datasource
8
+ strata ds test postgres_db # Using alias
@@ -0,0 +1,24 @@
1
+ Deploys your Strata project to the configured server. This command will:
2
+
3
+ 1. Run pre-deployment audit checks (unless --skip-audit is used)
4
+ 2. Check git repository status and ensure all changes are committed
5
+ 3. Create an archive of changed files since the last deployment
6
+ 4. Upload the archive to the Strata server
7
+ 5. Monitor deployment progress and display results
8
+
9
+ The deployment includes all YAML files in your project (models, datasources, tests).
10
+ Only files that have changed since the last successful deployment are included,
11
+ unless --force is used to deploy all files.
12
+
13
+ Options:
14
+ -e, --environment ENV Deploy to specific environment (dev, staging, prod)
15
+ --skip-audit Skip pre-deployment audit checks
16
+ --yes, -y Skip confirmation prompts (useful for CI/CD)
17
+ -f, --force Force deploy even if no files have changed
18
+
19
+ Examples:
20
+ strata deploy # Deploy to default environment
21
+ strata deploy -e production # Deploy to production environment
22
+ strata deploy --skip-audit # Skip pre-deployment checks
23
+ strata deploy --force # Force deploy even if no changes
24
+ strata deploy --yes # Skip confirmation prompts
@@ -0,0 +1,9 @@
1
+ Displays the current deployment status for the active git branch.
2
+ Shows deployment stage, status (succeeded/failed/in_progress), and test results if available.
3
+
4
+ Options:
5
+ -e, --environment ENV Check status for specific environment
6
+
7
+ Examples:
8
+ strata deploy status # Show status for current branch
9
+ strata deploy status -e production # Show status for production environment
@@ -0,0 +1,14 @@
1
+ Initialize a new Strata project or clone from an existing repository.
2
+
3
+ PROJECT_NAME is optional when using --source option. If provided, creates a new project
4
+ with that name. If using --source, clones the existing project from the URL.
5
+
6
+ Options:
7
+ -d, --datasource ADAPTER Add one or more datasource adapters (repeatable)
8
+ -s, --source URL URL of existing project to clone
9
+ -a, --api-key KEY API key (required when using --source)
10
+
11
+ Examples:
12
+ strata init my-project
13
+ strata init my-project -d postgres -d snowflake
14
+ strata init --source https://github.com/org/project.git --api-key YOUR_KEY
@@ -0,0 +1,83 @@
1
+ require_relative "group"
2
+ require "open3"
3
+
4
+ module Strata
5
+ module CLI
6
+ module Generators
7
+ class Datasource < Group
8
+ argument :adapter, type: :string, desc: "Data warehouse adapter to add as a datasource.", required: true
9
+ argument :name, type: :string, desc: "Optional name to be used as the key for the datasource.", required: false
10
+ class_option :path, type: :string, desc: "Need path when outside project directory"
11
+
12
+ def check_duckdb_requirements
13
+ return unless adapter.downcase == "duckdb"
14
+
15
+ unless duckdb_installed?
16
+ raise DWH::ConfigError,
17
+ "DuckDB is not installed. Please install DuckDB. We will need the header files to compile libraries."
18
+ end
19
+
20
+ install_duckdb_gem
21
+ end
22
+
23
+ def add_datasource_config
24
+ @ds_key = get_unique_ds_key
25
+ say_status :adapter, "adding #{adapter} config to datasources", :yellow
26
+
27
+ # Interactive mode: write config directly from prompts
28
+ config_yaml = {@ds_key => options[:config]}.to_yaml.sub(/^---\n/, "\n")
29
+ append_to_file pathify("datasources.yml"), config_yaml
30
+ end
31
+
32
+ private
33
+
34
+ def duckdb_installed?
35
+ # Check if DuckDB is installed globally by trying to run it
36
+ # Use Open3 for cross-platform compatibility (Windows doesn't support shell redirection)
37
+ _, _, status = Open3.capture3("duckdb", "--version")
38
+ status.success?
39
+ rescue Errno::ENOENT
40
+ # duckdb command not found
41
+ false
42
+ end
43
+
44
+ def install_duckdb_gem
45
+ say_status :gem, "Installing duckdb gem...", :yellow
46
+
47
+ begin
48
+ # Install the duckdb gem
49
+ run "gem install duckdb", verbose: false, capture: true
50
+ say_status :success, "duckdb gem installed successfully", :green
51
+ rescue => e
52
+ raise DWH::ConfigError, "Failed to install duckdb gem: #{e.message}"
53
+ end
54
+ end
55
+
56
+ def pathify(file)
57
+ options["path"].nil? ? file : "#{options["path"].gsub(%r{$/}, "")}/#{file}"
58
+ end
59
+
60
+ def get_unique_ds_key
61
+ base_key = (name && !name.strip.empty?) ? Utils.url_safe_str(name) : adapter
62
+ key_id = 1
63
+ ds_key = base_key
64
+ while current_ds.key?(ds_key)
65
+ ds_key = "#{base_key}_#{key_id}"
66
+ key_id += 1
67
+ end
68
+ ds_key
69
+ end
70
+
71
+ def current_ds
72
+ @ds ||= YAML.safe_load_file(pathify("datasources.yml"), permitted_classes: [Date, Time], aliases: true) || {}
73
+ end
74
+
75
+ def render_template(source, context: instance_eval("binding", __FILE__, __LINE__))
76
+ source = File.expand_path(find_in_source_paths(source.to_s))
77
+ capturable_erb = CapturableERB.new(::File.binread(source), trim_mode: "-", eoutvar: "@output_buffer")
78
+ capturable_erb.tap { |erb| erb.filename = source }.result(context)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,13 @@
1
+ module Strata::CLI::Generators
2
+ class Group < Thor::Group
3
+ include Thor::Actions
4
+
5
+ def self.source_root
6
+ File.expand_path("templates/", __dir__)
7
+ end
8
+
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "group"
4
+
5
+ module Strata
6
+ module CLI
7
+ module Generators
8
+ # Generates migration YAML files from templates.
9
+ class Migration < Group
10
+ argument :operation, type: :string, desc: "Migration operation: rename or swap"
11
+ argument :entity, type: :string, desc: "Entity type"
12
+ argument :from, type: :string, desc: "Source/current entity name"
13
+ argument :to, type: :string, desc: "Target/new entity name"
14
+
15
+ def create_migration_file
16
+ timestamp = generate_timestamp
17
+ filename = generate_filename(operation, entity, from, to, timestamp)
18
+ output_path = File.join("migrations", filename)
19
+
20
+ # Ensure directory exists
21
+ empty_directory "migrations"
22
+
23
+ # Load template and update with user inputs
24
+ template_content = load_template(operation)
25
+ hook = options[:hook] || ((operation == "swap") ? "post" : "pre")
26
+ updated_content = update_template(template_content, hook)
27
+
28
+ # Write the updated template
29
+ create_file output_path, updated_content
30
+
31
+ say_status :created, output_path, :green
32
+ end
33
+
34
+ private
35
+
36
+ def load_template(operation)
37
+ template_name = "migration.#{operation}.yml"
38
+ template_path = File.join(File.dirname(__FILE__), "templates", template_name)
39
+ File.read(template_path)
40
+ end
41
+
42
+ def update_template(template_content, hook)
43
+ template_content.gsub("<entity_type>", entity)
44
+ .gsub("<from_name>", from)
45
+ .gsub("<to_name>", to)
46
+ .gsub("<hook>", hook)
47
+ end
48
+
49
+ def generate_timestamp
50
+ Time.now.strftime("%Y%m%d%H%M%S")
51
+ end
52
+
53
+ def generate_filename(operation, entity, from, to, timestamp)
54
+ # Generate descriptive name following server pattern: timestamp_descriptive_name.yml
55
+ # Example: 20250101120000_rename_user_table.yml
56
+ from_slug = slugify(from)
57
+ to_slug = slugify(to)
58
+ descriptive_name = "#{operation}_#{from_slug}_to_#{to_slug}"
59
+ "#{timestamp}_#{descriptive_name}.yml"
60
+ end
61
+
62
+ def slugify(text)
63
+ text.to_s.downcase
64
+ .gsub(/[^a-z0-9]+/, "_")
65
+ .gsub(/^_|_$/, "")
66
+ .slice(0, 50) # Limit length
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,190 @@
1
+ require_relative "group"
2
+ require_relative "datasource"
3
+ require_relative "../helpers/project_helper"
4
+ require_relative "../api/connection_error_handler"
5
+ require "faraday"
6
+ require "json"
7
+ require "uri"
8
+
9
+ module Strata::CLI
10
+ module Generators
11
+ class Project < Group
12
+ include API::ConnectionErrorHandler
13
+
14
+ BASE_SERVER_URL = "http://localhost:3000"
15
+
16
+ argument :name, type: :string, required: false, desc: "The name of the project. Optional when using --source."
17
+ class_option :datasource, type: :string, repeatable: true
18
+ class_option :source, type: :string
19
+ class_option :api_key, type: :string
20
+
21
+ desc "Generates a new Strata project."
22
+
23
+ def validate_options
24
+ if options.key?(:source)
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?
28
+ 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
+
38
+ return unless project_info["git_url"] && !project_info["git_url"].empty?
39
+
40
+ @cloned_from_git = true
41
+ clone_project(project_info["git_url"])
42
+ end
43
+
44
+ def create_project_structure
45
+ return if cloned_from_git?
46
+
47
+ empty_directory uid
48
+ empty_directory File.join(uid, "models")
49
+ empty_directory File.join(uid, "tests")
50
+ end
51
+
52
+ def create_strata_config_file
53
+ template "strata.yml", "#{uid}/.strata"
54
+ end
55
+
56
+ def create_project_file
57
+ return if cloned_from_git?
58
+
59
+ template "project.yml", "#{uid}/project.yml"
60
+ end
61
+
62
+ def persist_project_id_if_needed
63
+ # Persist project_id after project.yml exists (either from clone or creation)
64
+ return unless @project_id
65
+
66
+ project_yml_path = File.join(uid, "project.yml")
67
+ return unless File.exist?(project_yml_path)
68
+
69
+ persist_project_id_to_project_yml
70
+ end
71
+
72
+ def create_datasources_file
73
+ return if cloned_from_git?
74
+
75
+ template "datasources.yml", "#{uid}/datasources.yml"
76
+
77
+ return unless options.key?(:datasource)
78
+
79
+ options[:datasource].each do |ds|
80
+ raise DWH::ConfigError, "Unsupported datasource #{ds}" unless DWH.adapter?(ds.to_sym)
81
+
82
+ Datasource.new([ds.downcase.strip], options.merge({"path" => uid})).invoke_all
83
+ end
84
+ end
85
+
86
+ def initialize_git
87
+ return if cloned_from_git?
88
+
89
+ inside uid do
90
+ run "git init", verbose: false, capture: true
91
+ create_file ".gitignore", ".strata\n"
92
+ end
93
+ end
94
+
95
+ def setup_datasource
96
+ return if cloned_from_git?
97
+ return if options.key?(:datasource) # Already specified via CLI option
98
+
99
+ say "\n", :white
100
+ say_status :setup, "Let's configure your first datasource", :cyan
101
+
102
+ # Change into the project directory and run the existing add command
103
+ inside(uid) do
104
+ require_relative "../sub_commands/datasource"
105
+ SubCommands::Datasource.new.add
106
+ end
107
+ end
108
+
109
+ def completion_message
110
+ say "\n✔ Strata project '#{uid}' is ready!", :green
111
+ say "\nNext steps:", :yellow
112
+ say " 1. cd #{uid}", :cyan
113
+ say " 2. strata datasource add # To add more datasources", :cyan
114
+ say " 3. strata create table # Start adding tables", :cyan
115
+ say "\n"
116
+ end
117
+
118
+ private
119
+
120
+ def cloned_from_git?
121
+ @cloned_from_git ||= false
122
+ end
123
+
124
+ def fetch_project_info
125
+ conn = Faraday.new(url: options[:source]) do |f|
126
+ f.request :authorization, "Bearer", options[:api_key]
127
+ f.response :json
128
+ end
129
+
130
+ response = with_connection_error_handling(options[:source]) do
131
+ conn.get
132
+ end
133
+
134
+ unless response.success?
135
+ raise Strata::CommandError,
136
+ "Failed to fetch project info from #{options[:source]}. Status: #{response.status}"
137
+ end
138
+
139
+ response.body
140
+ end
141
+
142
+ def clone_project(git_url)
143
+ say_status :clone, "Cloning project from #{git_url}", :green
144
+ run "git clone #{git_url} #{uid}", verbose: false, capture: true
145
+
146
+ raise Strata::CommandError, "Failed to clone repository from #{git_url}" unless File.directory?(uid)
147
+
148
+ say_status :success, "Project cloned successfully", :green
149
+ end
150
+
151
+ def persist_project_id_to_project_yml
152
+ return unless @project_id
153
+
154
+ project_yml_path = File.join(uid, "project.yml")
155
+ Helpers::ProjectHelper.persist_project_id_to_yml(@project_id, project_yml_path: project_yml_path)
156
+ end
157
+
158
+ def uid
159
+ @uid ||= if options.key?(:source) && @project_info
160
+ @project_info["uid"] || options[:source].split("/").last
161
+ else
162
+ Utils.url_safe_str(name)
163
+ end
164
+ end
165
+
166
+ def name
167
+ # Use name from server if available (when using --source), otherwise use argument
168
+ return @project_info["name"] if options.key?(:source) && @project_info
169
+ @name
170
+ end
171
+
172
+ def server_url
173
+ return options[:source] if options.key?(:source)
174
+ BASE_SERVER_URL
175
+ end
176
+
177
+ def description
178
+ return @project_info["description"] if options.key?(:source) && @project_info
179
+ nil
180
+ end
181
+
182
+ def production_branch
183
+ return @project_info["production_branch"] if options.key?(:source) && @project_info && @project_info["production_branch"]
184
+ "main"
185
+ end
186
+
187
+ attr_reader :project_id
188
+ end
189
+ end
190
+ end