strata-cli 0.1.14 → 0.1.16

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.
@@ -7,6 +7,7 @@ require_relative "../output"
7
7
  require_relative "../utils/git"
8
8
  require "tty-prompt"
9
9
  require_relative "../helpers/datasource_helper"
10
+ require_relative "../helpers/prompts"
10
11
  require_relative "../helpers/description_helper"
11
12
  require_relative "../agent_mode"
12
13
 
@@ -62,8 +63,6 @@ module Strata
62
63
  desc "add [ADAPTER]", "Add a new datasource interactively"
63
64
  long_desc_from_file "datasource/add"
64
65
  def add(adapter_name = nil)
65
- prompt = TTY::Prompt.new
66
-
67
66
  if adapter_name && !DWH.adapters.keys.map(&:to_s).include?(adapter_name)
68
67
  say "Error: '#{adapter_name}' is not a supported adapter", :red
69
68
  say "Supported adapters: #{DWH.adapters.keys.join(", ")}", :yellow
@@ -132,6 +131,11 @@ module Strata
132
131
  end
133
132
 
134
133
  say "\n✔ Datasource '#{ds_key}' is ready!", :green
134
+
135
+ # 'strata init' runs this command inline and prints its own next steps after.
136
+ return if options["from_init"] || agent_mode?
137
+
138
+ say "\n#{Prompts.build_model_next_steps}", :cyan
135
139
  end
136
140
 
137
141
  desc "auth DS_KEY", "Set credentials for the given datasource key (DS_KEY)."
@@ -182,8 +186,7 @@ module Strata
182
186
  method_option :catalog, aliases: "c", type: :string, desc: "Change the catalog from the configured one."
183
187
  method_option :schema, aliases: "s", type: :string, desc: "Change the schema from the configured one."
184
188
  def tables(ds_key = nil)
185
- prompt = TTY::Prompt.new
186
- ds_key = resolve_datasource(ds_key, prompt: prompt)
189
+ ds_key = resolve_datasource(ds_key)
187
190
  return unless ds_key
188
191
 
189
192
  adapter = create_adapter(ds_key)
@@ -5,6 +5,7 @@ require_relative "../terminal"
5
5
  require_relative "../output"
6
6
  require_relative "../helpers/project_helper"
7
7
  require_relative "../helpers/description_helper"
8
+ require_relative "../helpers/browser_auth"
8
9
  require_relative "../agent_mode"
9
10
  require_relative "../api/client"
10
11
  require_relative "../credentials"
@@ -25,6 +26,7 @@ module Strata
25
26
  include Terminal
26
27
  include Output
27
28
  include AgentMode
29
+ include Helpers::BrowserAuth
28
30
  extend Helpers::DescriptionHelper
29
31
 
30
32
  default_command :deploy
@@ -37,6 +39,9 @@ module Strata
37
39
  desc "deploy", "Deploy project to Strata server for the current branch"
38
40
  long_desc_from_file "deploy/deploy"
39
41
  def deploy
42
+ # Before anything that prompts, signs in, or hits the server.
43
+ ensure_working_tree_clean
44
+
40
45
  config = load_deployment_configuration
41
46
  validate_deployment_configuration(config)
42
47
  run_pre_deploy_checks unless options[:skip_audit]
@@ -128,8 +133,9 @@ module Strata
128
133
  end
129
134
 
130
135
  def validate_deployment_configuration(config)
131
- ensure_api_key(config)
136
+ # Server first: browser sign-in needs its URL to build the auth link
132
137
  ensure_server(config)
138
+ ensure_api_key(config)
133
139
  ensure_git_url_populated
134
140
  ensure_project_id(config)
135
141
  end
@@ -139,7 +145,7 @@ module Strata
139
145
  return
140
146
  end
141
147
 
142
- config["api_key"] = collect_api_key_interactively
148
+ config["api_key"] = collect_api_key_interactively(config["server"])
143
149
  end
144
150
 
145
151
  def ensure_server(config)
@@ -149,8 +155,6 @@ module Strata
149
155
  end
150
156
 
151
157
  def collect_server_interactively
152
- prompt = TTY::Prompt.new
153
-
154
158
  say "\nServer URL not found (not in project.yml)", :yellow
155
159
  say "You can add it to project.yml or enter it now here\n", :cyan
156
160
 
@@ -199,16 +203,24 @@ module Strata
199
203
  persist_project_id(project_id)
200
204
  end
201
205
 
202
- def collect_api_key_interactively
203
- prompt = TTY::Prompt.new
204
-
206
+ def collect_api_key_interactively(server)
205
207
  say "\nAPI key not found in .strata file", :yellow
206
- say "\nTo deploy to Strata, you need an API key.\nYou can get your API key from:", :white
207
- say " • Your Strata server dashboard (Settings → API Keys)\n • Your Strata administrator\n", :cyan
208
208
 
209
- api_key = prompt.mask("Enter your Strata API key:") do |q|
210
- q.required true
211
- q.validate(/\S+/, "API key cannot be empty")
209
+ choice = prompt.select("How would you like to authenticate?") do |menu|
210
+ menu.choice "Sign in via browser (recommended)", :browser
211
+ menu.choice "Paste an API key", :manual
212
+ end
213
+
214
+ api_key = if choice == :browser
215
+ fetch_api_key_via_browser(server)
216
+ else
217
+ say "\nYou can get your API key from:", :white
218
+ say " • Your Strata server dashboard (Settings → API Keys)\n • Your Strata administrator\n", :cyan
219
+
220
+ prompt.mask("Enter your Strata API key:") do |q|
221
+ q.required true
222
+ q.validate(/\S+/, "API key cannot be empty")
223
+ end
212
224
  end
213
225
 
214
226
  # Save API key to .strata file
@@ -272,15 +284,15 @@ module Strata
272
284
  raise Strata::CommandError, "Audit checks failed. Fix errors before deploying." if e.status != 0
273
285
  end
274
286
 
275
- def run_git_status_checks(branch_name)
287
+ def ensure_working_tree_clean
276
288
  return unless Utils::Git.git_repo?
289
+ return unless Utils::Git.uncommitted_changes?
277
290
 
278
- with_spinner("Checking git repository status") do
279
- # Check for uncommitted changes
280
- if Utils::Git.uncommitted_changes?
281
- raise Strata::CommandError, "You have uncommitted changes. Please commit or stash them before deploying."
282
- end
283
- end
291
+ raise Strata::CommandError, "You have uncommitted changes. Please commit or stash them before deploying."
292
+ end
293
+
294
+ def run_git_status_checks(branch_name)
295
+ return unless Utils::Git.git_repo?
284
296
 
285
297
  # Fetch from remote
286
298
  with_spinner("Fetching latest changes from remote") do
@@ -309,10 +321,16 @@ module Strata
309
321
  commit_info = Utils::Git.commit_info
310
322
  committer_info = Utils::Git.committer_info
311
323
 
312
- # Get file modifications since last deployment or since HEAD~1
324
+ # Get file modifications since the last deployment. Without a valid baseline
325
+ # commit we can't compute an incremental diff, so send the full file list
326
+ # rather than silently narrowing to the last commit.
313
327
  file_modifications = if last_deployment_commit
314
328
  Utils::Git.changed_files_since(last_deployment_commit)
315
329
  else
330
+ print_warning(
331
+ "No valid previous deployment commit to diff against — sending all files. " \
332
+ "Renames can't be auto-detected this deploy; verify renamed tables afterward or use a rename migration."
333
+ )
316
334
  Utils::Git.file_modifications
317
335
  end
318
336
 
@@ -411,7 +429,6 @@ module Strata
411
429
  production_branch = get_production_branch(config)
412
430
  return unless branch_id == production_branch
413
431
 
414
- prompt = TTY::Prompt.new
415
432
  message = "Deploying to production branch '#{branch_id}'. Continue? [y/N]"
416
433
  return if prompt.yes?(message, default: false)
417
434
 
@@ -439,6 +456,13 @@ module Strata
439
456
  return nil unless last_deployment && last_deployment["commit"]
440
457
 
441
458
  commit_hash = last_deployment["commit"]
459
+ unless Utils::Git.validate_commit_hash(commit_hash)
460
+ print_warning(
461
+ "Last deployment commit #{commit_hash.inspect} is not a valid git SHA " \
462
+ "(likely a non-git deploy). Can't compute an incremental diff; falling back to a full-file deploy.\n"
463
+ )
464
+ return nil
465
+ end
442
466
  print_info("Found last successful deployment at commit: #{commit_hash[0..7]}...\n")
443
467
  commit_hash
444
468
  rescue Strata::CommandError
@@ -4,9 +4,12 @@ require_relative "../guard"
4
4
  require_relative "../terminal"
5
5
  require_relative "../output"
6
6
  require_relative "../helpers/project_helper"
7
+ require_relative "../helpers/browser_auth"
7
8
  require_relative "../agent_mode"
8
9
  require_relative "../api/client"
9
10
  require "yaml"
11
+ require "fileutils"
12
+ require "tty-prompt"
10
13
 
11
14
  module Strata
12
15
  module CLI
@@ -16,6 +19,50 @@ module Strata
16
19
  include Terminal
17
20
  include Output
18
21
  include AgentMode
22
+ include Helpers::BrowserAuth
23
+
24
+ SAMPLE_REPO = "https://github.com/stratasite/tpc-ds-retail-sample"
25
+ SAMPLE_DIR = "tpc-ds-retail-sample"
26
+ SAMPLE_PROJECT_UID = "tpc-ds-retail-sample"
27
+ DEFAULT_SERVER = "http://localhost:8080"
28
+
29
+ desc "sample", "Clone the Strata sample project and set it up interactively"
30
+ option :api_key, aliases: ["a"], type: :string, desc: "Strata API key (optional — skip browser auth)"
31
+ option :server, aliases: ["s"], type: :string, desc: "Strata server URL (optional — prompted if omitted)"
32
+ def sample
33
+ if options[:api_key] && options[:server]
34
+ server = options[:server].delete_suffix("/")
35
+ api_key = options[:api_key]
36
+ else
37
+ server = (options[:server] || prompt.ask("Strata server URL:", default: DEFAULT_SERVER) { |q| q.modify :strip }).delete_suffix("/")
38
+ api_key = fetch_api_key_via_browser(server)
39
+ end
40
+
41
+ if File.directory?(SAMPLE_DIR)
42
+ print_info("Directory '#{SAMPLE_DIR}' already exists — re-using it")
43
+ else
44
+ print_info("Cloning sample project from #{SAMPLE_REPO}...")
45
+ system("git clone #{SAMPLE_REPO} #{SAMPLE_DIR}", exception: true)
46
+ end
47
+
48
+ Dir.chdir(SAMPLE_DIR) do
49
+ FileUtils.rm_rf(".git")
50
+ system("git init -b main", exception: true)
51
+ File.write(".gitignore", ".strata\n.strata_cli/\n")
52
+
53
+ strata_path = ".strata"
54
+ File.write(strata_path, "api_key: #{api_key}\nserver: #{server}\n")
55
+ File.chmod(0o600, strata_path)
56
+
57
+ Helpers::ProjectHelper.persist_project_id_to_yml(SAMPLE_PROJECT_UID)
58
+
59
+ system("git add -A", exception: true)
60
+ system('git commit -m "Initial sample project setup"', exception: true)
61
+ end
62
+
63
+ print_success("✔ Sample project '#{SAMPLE_DIR}' is ready!")
64
+ print_info(" Next: cd #{SAMPLE_DIR} && strata deploy")
65
+ end
19
66
 
20
67
  desc "link PROJECT_ID", "Link local project to an existing project on the server"
21
68
  def link(project_id)
@@ -88,12 +88,17 @@ module Strata
88
88
  }
89
89
  end
90
90
 
91
+ # The file-modifications payload to send when there is no valid baseline
92
+ # commit to diff against: the full list of tracked YAML model files, so no
93
+ # added/modified file is silently dropped. NOTE: renames cannot be detected
94
+ # without a baseline — in this mode a rename appears as add + delete.
91
95
  def file_modifications
92
- diff_output = run_git_command("diff", "--name-status", "HEAD~1")
93
- return [] unless diff_output
96
+ return [] unless git_repo?
94
97
 
95
- modifications = parse_diff_output(diff_output)
96
- filter_yml_files(modifications)
98
+ output = run_git_command("ls-files", "*.yml", "*.yaml")
99
+ return [] unless output
100
+
101
+ output.lines.map(&:strip).reject(&:empty?).map { |path| ["M", path] }
97
102
  end
98
103
 
99
104
  def changed_files_since(commit_hash)
@@ -108,8 +113,9 @@ module Strata
108
113
  _, _, status = Open3.capture3("git", "rev-parse", "--verify", "#{commit_hash}^{commit}")
109
114
  return [] unless status.success?
110
115
 
111
- # Get all changed files since that commit (including added, modified, deleted, renamed)
112
- diff_output = run_git_command("diff", "--name-status", commit_hash, "HEAD")
116
+ # Get all changed files since that commit (including added, modified, deleted, renamed).
117
+ # --find-renames makes rename (R) detection explicit rather than relying on git config.
118
+ diff_output = run_git_command("diff", "--name-status", "--find-renames", commit_hash, "HEAD")
113
119
  return [] unless diff_output
114
120
 
115
121
  modifications = parse_diff_output(diff_output)
@@ -130,7 +136,7 @@ module Strata
130
136
 
131
137
  # Get file paths that changed (for added and modified files only, exclude deleted)
132
138
  # --diff-filter=ACMR: Added, Copied, Modified, Renamed (excludes Deleted)
133
- diff_output = run_git_command("diff", "--name-status", "--diff-filter=ACMR", commit_hash, "HEAD")
139
+ diff_output = run_git_command("diff", "--name-status", "--find-renames", "--diff-filter=ACMR", commit_hash, "HEAD")
134
140
  return [] unless diff_output
135
141
 
136
142
  paths = []
@@ -228,9 +234,9 @@ module Strata
228
234
  end
229
235
 
230
236
  def parse_diff_output(output)
231
- output.lines.map do |line|
237
+ output.lines.filter_map do |line|
232
238
  parse_diff_status(line.strip)
233
- end.compact
239
+ end
234
240
  end
235
241
 
236
242
  def parse_diff_status(line)
@@ -314,6 +320,22 @@ module Strata
314
320
  end
315
321
  end
316
322
 
323
+ # Commits everything in a freshly initialized repo. Skips a repo that already
324
+ # has history, so running this inside an existing project never touches it.
325
+ # Returns :committed, :skipped or :failed (usually an unset user.name/email).
326
+ def initial_commit(message)
327
+ return :skipped unless git_repo?
328
+
329
+ _, _, head = Open3.capture3("git", "rev-parse", "--verify", "HEAD")
330
+ return :skipped if head.success?
331
+
332
+ _, _, add = Open3.capture3("git", "add", "-A")
333
+ return :failed unless add.success?
334
+
335
+ _, _, commit = Open3.capture3("git", "commit", "-m", message)
336
+ commit.success? ? :committed : :failed
337
+ end
338
+
317
339
  def commit_file(file_path, commit_message, project_path = Dir.pwd)
318
340
  return false unless git_repo?
319
341
 
@@ -0,0 +1,153 @@
1
+ # Vendored from strata (server) lib/sql/expression_parser.rb, restyled to standard — keep the logic in sync.
2
+ module Strata
3
+ module CLI
4
+ module Sql
5
+ class ExpressionParser
6
+ attr_reader :expression, :columns, :field_refs, :measure_refs, :dimension_refs, :functions
7
+
8
+ def initialize(expression, reserved: DWH.reserved_keywords, aggregates: DWH.aggregate_functions)
9
+ @expression = expression.downcase
10
+ @reserved = reserved
11
+ @aggregates = aggregates
12
+ @columns = []
13
+ @field_refs = []
14
+ @measure_refs = []
15
+ @dimension_refs = []
16
+ @functions = []
17
+ @wild = false
18
+ parse
19
+ end
20
+
21
+ def wild?
22
+ @wild
23
+ end
24
+
25
+ # Does this expression apply an aggregate function (sum, max, count, ...)?
26
+ # Used to decide whether a calculation behaves as a measure.
27
+ def has_aggregate_functions?
28
+ @functions.any? { |f| @aggregates.include?(f) }
29
+ end
30
+
31
+ # Does the expression carry a window (`... over (...)`)? Windows are valid wherever the
32
+ # expression evaluates over final rows — including a non-grouped query — but can never
33
+ # sit inside a GROUP BY.
34
+ def has_window_functions?
35
+ !!@windowed
36
+ end
37
+
38
+ # The @d dimension references that sit outside every aggregate call. In an
39
+ # aggregating expression these are what SQL demands be grouped: a dimension
40
+ # inside sum(case when [Region]@d = 'x' …) is fine, one in
41
+ # case when [Region]@d is null then null else sum(…) end is not unless
42
+ # the query groups by Region.
43
+ def unaggregated_dimension_refs
44
+ @unaggregated_dimension_refs ||= begin
45
+ refs = []
46
+ stack = [] # one entry per open paren: is it an aggregate call?
47
+ # Bracketed references are consumed atomically FIRST, so a parenthesis inside a
48
+ # reference name ([Month(Date)]) cannot unbalance the stack.
49
+ remove_string_literals(@expression).scan(/\[([^\]]+)\]@d|\[[^\]]+\](?:@m)?|([a-z_][a-z0-9_]*)?\(|\)/) do
50
+ token = Regexp.last_match(0)
51
+ if Regexp.last_match(1)
52
+ ref = Regexp.last_match(1).strip
53
+ refs << ref unless stack.any? || refs.include?(ref)
54
+ elsif token.start_with?("[")
55
+ next # an @m or alias reference: opaque
56
+ elsif token == ")"
57
+ stack.pop
58
+ else
59
+ stack << @aggregates.include?(Regexp.last_match(2).to_s)
60
+ end
61
+ end
62
+ refs
63
+ end
64
+ end
65
+
66
+ class << self
67
+ # Can this (resolved) SQL expression legally appear in a GROUP BY? Aggregates and
68
+ # windows evaluate over the grouped rows, so an expression carrying either cannot —
69
+ # the binder rejects it ("GROUP BY clause cannot contain aggregates").
70
+ def ungroupable?(sql)
71
+ parsed = parse(sql)
72
+ parsed.has_aggregate_functions? || parsed.has_window_functions?
73
+ end
74
+
75
+ def entity_gsub(sql, entity_name, replacement)
76
+ sql.gsub(/\[\s*#{Regexp.quote(entity_name)}\s*\](@[mdMD])?/i, replacement)
77
+ end
78
+
79
+ # Qualify a column reference within sql with table_alias and adapter quoting.
80
+ # Handles both double-quoted identifiers ("limit") and bare identifiers (amount).
81
+ # The quoted form is replaced first; negative lookarounds on the bare-word pass
82
+ # prevent re-matching col that now appears inside the qualified replacement.
83
+ def column_gsub(sql, col, adapter, table_alias)
84
+ qualified = "#{table_alias}.#{adapter.quote(col)}"
85
+ sql = sql.gsub("\"#{col}\"", qualified)
86
+ sql.gsub(/(?<!")\b#{Regexp.quote(col)}\b(?!")/i, qualified)
87
+ end
88
+
89
+ def parse(expression, reserved: DWH.reserved_keywords, aggregates: DWH.aggregate_functions)
90
+ new(expression, reserved: reserved, aggregates: aggregates)
91
+ end
92
+ end
93
+
94
+ private
95
+
96
+ def parse
97
+ # Sanitize the expression first
98
+ sanitized = remove_string_literals(@expression)
99
+
100
+ matched_refs = []
101
+ # Extract field references in bracketed notation — BEFORE scanning functions, so a
102
+ # parenthesis inside a reference name ([Month(Date)]) cannot masquerade as a call.
103
+ sanitized.scan(/\[([^\]]+)\](@[md])?/).each do |ref, type|
104
+ matched_refs << "[#{ref}]#{type}"
105
+ case type
106
+ when "@m"
107
+ @measure_refs << ref.strip unless @measure_refs.include?(ref)
108
+ when "@d"
109
+ @dimension_refs << ref.strip unless @dimension_refs.include?(ref)
110
+ else
111
+ @field_refs << ref.strip unless @field_refs.include?(ref)
112
+ end
113
+ end
114
+
115
+ sanitized = remove_matched_refs(sanitized, matched_refs)
116
+
117
+ # Extract functions
118
+ sanitized.scan(/\b([a-zA-Z_][a-zA-Z0-9_]*)\(/).each do |func|
119
+ @functions << func.first
120
+ end
121
+
122
+ # A window clause marks the expression as computed over the final rows — after any
123
+ # grouping — which is what has_window_functions? reports.
124
+ @windowed = sanitized.match?(/\bover\s*\(/)
125
+
126
+ # Extract quoted columns
127
+ @expression.scan(/"([^"]+)"/).flatten.each do |col|
128
+ @columns << col
129
+ end
130
+
131
+ # Extract column references (alphanumeric with optional underscores)
132
+ sanitized.scan(/\b([a-zA-Z_][a-zA-Z0-9_]*)\b/).flatten.each do |col|
133
+ @columns << col unless @reserved.include?(col) || @functions.include?(col) || @columns.include?(col)
134
+ end
135
+
136
+ @wild = true if sanitized.match?(/\w+\(\s?\*\s?\)/)
137
+ end
138
+
139
+ def remove_string_literals(expr)
140
+ expr.gsub(/'[^']*'/, "'__str__'")
141
+ end
142
+
143
+ def remove_matched_refs(sanitized, matched_refs)
144
+ l = sanitized
145
+ matched_refs.each do |ref|
146
+ l.gsub!(ref, "")
147
+ end
148
+ l
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Strata
4
4
  module CLI
5
- VERSION = "0.1.14"
5
+ VERSION = "0.1.16"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strata-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.16
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ajo Abraham
@@ -264,7 +264,6 @@ files:
264
264
  - lib/strata/cli/descriptions/deploy/deploy.txt
265
265
  - lib/strata/cli/descriptions/deploy/status.txt
266
266
  - lib/strata/cli/descriptions/init.txt
267
- - lib/strata/cli/descriptions/tutorial.txt
268
267
  - lib/strata/cli/error_reporter.rb
269
268
  - lib/strata/cli/generators/datasource.rb
270
269
  - lib/strata/cli/generators/group.rb
@@ -293,13 +292,14 @@ files:
293
292
  - lib/strata/cli/generators/templates/table.table_name.yml
294
293
  - lib/strata/cli/generators/templates/test.yml
295
294
  - lib/strata/cli/generators/test.rb
296
- - lib/strata/cli/generators/tutorial.rb
297
295
  - lib/strata/cli/guard.rb
296
+ - lib/strata/cli/helpers/browser_auth.rb
298
297
  - lib/strata/cli/helpers/command_context.rb
299
298
  - lib/strata/cli/helpers/datasource_helper.rb
300
299
  - lib/strata/cli/helpers/description_helper.rb
301
300
  - lib/strata/cli/helpers/project_helper.rb
302
301
  - lib/strata/cli/helpers/prompts.rb
302
+ - lib/strata/cli/helpers/semantic_audit_checks.rb
303
303
  - lib/strata/cli/helpers/table_filter.rb
304
304
  - lib/strata/cli/main.rb
305
305
  - lib/strata/cli/output.rb
@@ -318,6 +318,7 @@ files:
318
318
  - lib/strata/cli/utils/deployment_monitor.rb
319
319
  - lib/strata/cli/utils/git.rb
320
320
  - lib/strata/cli/utils/import_manager.rb
321
+ - lib/strata/cli/utils/sql_expression_parser.rb
321
322
  - lib/strata/cli/utils/test_reporter.rb
322
323
  - lib/strata/cli/utils/version_checker.rb
323
324
  - lib/strata/cli/utils/yaml_import_resolver.rb
@@ -342,7 +343,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
342
343
  - !ruby/object:Gem::Version
343
344
  version: '0'
344
345
  requirements: []
345
- rubygems_version: 3.6.7
346
+ rubygems_version: 3.6.9
346
347
  specification_version: 4
347
348
  summary: Command-line interface for the Strata Semantic Analytics System
348
349
  test_files: []
@@ -1,13 +0,0 @@
1
- Sets up the Strata tutorial project locally by cloning the tutorial repo,
2
- wiring it to your Strata instance, and preparing it for `strata deploy`.
3
-
4
- The --api-key value is shown on the tutorial setup page
5
- in your Strata instance. Copy-paste them directly from there.
6
-
7
- After running this command, `cd` into the project directory and run
8
- `strata deploy` to deploy the tutorial to your Strata instance.
9
-
10
- Example:
11
- strata tutorial -a your_api_key
12
- cd tutorial
13
- strata deploy
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "group"
4
- require_relative "../helpers/project_helper"
5
- require_relative "../output"
6
- require "yaml"
7
- require "fileutils"
8
-
9
- module Strata
10
- module CLI
11
- module Generators
12
- class Tutorial < Group
13
- TUTORIAL_REPO = "https://github.com/stratasite/strata-tutorial-project"
14
- DEFAULT_SERVER = "http://localhost:8080"
15
- TUTORIAL_PROJECT = "strata-tutorial-project"
16
-
17
- class_option :api_key, aliases: ["a"], type: :string, desc: "Strata API key"
18
-
19
- desc "Sets up the Strata tutorial project locally."
20
-
21
- def validate_options
22
- raise Strata::CommandError, "--api-key (-a) is required." unless options[:api_key]
23
- @uid = TUTORIAL_PROJECT
24
- @project_id = 1
25
- end
26
-
27
- def clone_tutorial_repo
28
- if File.directory?(@uid)
29
- Output.print_status(:skip, "Directory '#{@uid}' already exists — re-using it", type: :info, context: self)
30
- return
31
- end
32
- Output.print_status(:clone, "Cloning tutorial from #{TUTORIAL_REPO}", type: :success, context: self)
33
- run "git clone #{TUTORIAL_REPO} #{@uid}", verbose: false, capture: true
34
- raise Strata::CommandError, "Failed to clone tutorial repo." unless File.directory?(@uid)
35
- end
36
-
37
- def reinitialize_git
38
- inside @uid do
39
- FileUtils.rm_rf(".git")
40
- run "git init -b main", verbose: false, capture: true
41
- create_file ".gitignore", ".strata\n.strata_cli/\n", force: true
42
- end
43
- end
44
-
45
- def write_strata_config
46
- strata_path = File.join(@uid, ".strata")
47
- File.write(strata_path, "api_key: #{options[:api_key]}\nserver: #{DEFAULT_SERVER}\n")
48
- File.chmod(0o600, strata_path)
49
- end
50
-
51
- def update_project_yml
52
- project_yml = File.join(@uid, "project.yml")
53
- return unless File.exist?(project_yml)
54
-
55
- Helpers::ProjectHelper.persist_project_id_to_yml(@project_id, project_yml_path: project_yml)
56
- end
57
-
58
- def initial_commit
59
- inside @uid do
60
- run "git add -A", verbose: false, capture: true
61
- run 'git commit -m "Initial tutorial project setup"', verbose: false, capture: true
62
- end
63
- end
64
-
65
- def completion_message
66
- Output.print_success("\n✔ Tutorial project '#{@uid}' is ready!\n", context: self)
67
- Output.print_info(" Next: cd #{@uid} && strata deploy --skip-audit\n", context: self)
68
- end
69
- end
70
- end
71
- end
72
- end