create-rails-app 0.1.0 → 0.2.0

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.
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CreateRailsApp
4
+ module UI
5
+ # Color constants for terminal output.
6
+ #
7
+ # Maps semantic roles (e.g. +:summary_label+, +:arg_name+) to ANSI 256-color
8
+ # codes or +cli-ui+ basic color names, depending on terminal capabilities.
9
+ class Palette
10
+ # ANSI reset sequence.
11
+ RESET = "\e[0m"
12
+
13
+ # Role-to-color mappings for 256-color terminals.
14
+ #
15
+ # @return [Hash{Symbol => Integer}]
16
+ ROLE_COLORS_256 = {
17
+ control_back: 45,
18
+ control_exit: 203,
19
+ summary_label: 213,
20
+ runtime_name: 111,
21
+ runtime_value: 190,
22
+ command_base: 39,
23
+ command_app: 82,
24
+ arg_name: 117,
25
+ arg_eq: 250,
26
+ arg_value: 214,
27
+ install_cmd: 178,
28
+ exit_message: 82
29
+ }.freeze
30
+
31
+ # Role-to-color mappings for basic (8/16-color) terminals.
32
+ #
33
+ # @return [Hash{Symbol => String}]
34
+ ROLE_COLORS_BASIC = {
35
+ control_back: 'cyan',
36
+ control_exit: 'red',
37
+ summary_label: 'magenta',
38
+ runtime_name: 'blue',
39
+ runtime_value: 'green',
40
+ command_base: 'blue',
41
+ command_app: 'green',
42
+ arg_name: 'blue',
43
+ arg_eq: 'white',
44
+ arg_value: 'yellow',
45
+ install_cmd: 'yellow',
46
+ exit_message: 'green'
47
+ }.freeze
48
+
49
+ # @param env [Hash] environment variables (defaults to +ENV+)
50
+ def initialize(env: ENV)
51
+ @env = env
52
+ end
53
+
54
+ # Wraps text in the appropriate ANSI color for the given role.
55
+ #
56
+ # @param role [Symbol] a key from {ROLE_COLORS_256}/{ROLE_COLORS_BASIC}
57
+ # @param text [String] the text to colorize
58
+ # @return [String]
59
+ def color(role, text)
60
+ if no_color?
61
+ ROLE_COLORS_256.fetch(role) # validate role key exists
62
+ text
63
+ elsif supports_256_colors?
64
+ "\e[38;5;#{ROLE_COLORS_256.fetch(role)}m#{text}#{RESET}"
65
+ else
66
+ "{{#{ROLE_COLORS_BASIC.fetch(role)}:#{text}}}"
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ # @return [Boolean]
73
+ def no_color?
74
+ @env.key?('NO_COLOR') || @env['TERM'] == 'dumb'
75
+ end
76
+
77
+ # @return [Boolean]
78
+ def supports_256_colors?
79
+ return false if no_color?
80
+
81
+ term = @env.fetch('TERM', '')
82
+ colorterm = @env.fetch('COLORTERM', '')
83
+ term.include?('256color') || colorterm.match?(/truecolor|24bit/i)
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cli/ui'
4
+
5
+ module CreateRailsApp
6
+ module UI
7
+ # Raised when the user presses Ctrl+B during a prompt.
8
+ class BackKeyPressed < StandardError; end
9
+
10
+ # Thin wrapper around +cli-ui+ for all user interaction.
11
+ #
12
+ # Every prompt the wizard issues goes through this class, making it
13
+ # easy to inject a test double.
14
+ class Prompter
15
+ CTRL_B = "\u0002"
16
+
17
+ module ReadCharPatch
18
+ def read_char
19
+ char = super
20
+ raise BackKeyPressed if char == CTRL_B
21
+
22
+ char
23
+ end
24
+ end
25
+
26
+ # Enables the +cli-ui+ stdout router and applies the Ctrl+B patch.
27
+ #
28
+ # Call once before creating a Prompter instance. Idempotent.
29
+ #
30
+ # @return [void]
31
+ def self.setup!
32
+ ::CLI::UI::StdoutRouter.enable
33
+
34
+ unless ::CLI::UI::Prompt.respond_to?(:read_char)
35
+ raise Error, 'CLI::UI::Prompt does not respond to read_char; back-navigation patch cannot be applied'
36
+ end
37
+
38
+ singleton = ::CLI::UI::Prompt.singleton_class
39
+ return if singleton.ancestors.include?(ReadCharPatch)
40
+
41
+ singleton.prepend(ReadCharPatch)
42
+ end
43
+
44
+ # @param out [IO] output stream
45
+ def initialize(out: $stdout)
46
+ @out = out
47
+ end
48
+
49
+ # Opens a visual frame with a title.
50
+ #
51
+ # @param title [String]
52
+ # @yield block executed inside the frame
53
+ # @return [void]
54
+ def frame(title, &)
55
+ ::CLI::UI::Frame.open(title, &)
56
+ end
57
+
58
+ # Presents a single-choice list.
59
+ #
60
+ # @param question [String]
61
+ # @param options [Array<String>]
62
+ # @param default [String, nil]
63
+ # @return [String] selected option, or {Wizard::BACK} on Ctrl+B
64
+ def choose(question, options:, default: nil)
65
+ ::CLI::UI.ask(question, options: options, default: default, filter_ui: false)
66
+ rescue BackKeyPressed
67
+ Wizard::BACK
68
+ end
69
+
70
+ # Prompts for free-text input.
71
+ #
72
+ # @param question [String]
73
+ # @param default [String, nil]
74
+ # @param allow_empty [Boolean]
75
+ # @return [String]
76
+ def text(question, default: nil, allow_empty: true)
77
+ ::CLI::UI.ask(question, default: default, allow_empty: allow_empty)
78
+ rescue BackKeyPressed
79
+ Wizard::BACK
80
+ end
81
+
82
+ # Prompts for yes/no confirmation.
83
+ #
84
+ # @param question [String]
85
+ # @param default [Boolean]
86
+ # @return [Boolean]
87
+ def confirm(question, default: true)
88
+ ::CLI::UI.confirm(question, default: default)
89
+ rescue BackKeyPressed
90
+ # Confirm callers (preset save, overwrite prompts) don't handle BACK;
91
+ # swallowing the key press and returning the default is intentional.
92
+ default
93
+ end
94
+
95
+ # Prints a formatted message to the output stream.
96
+ #
97
+ # @param message [String] message with optional +cli-ui+ formatting tags
98
+ # @return [void]
99
+ def say(message)
100
+ @out.puts(::CLI::UI.fmt(message))
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CreateRailsApp
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -0,0 +1,368 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CreateRailsApp
4
+ # Step-by-step interactive prompt loop for choosing +rails new+ options.
5
+ #
6
+ # Walks through each supported option in {Options::Catalog::ORDER},
7
+ # presenting only the options supported by the detected Rails version.
8
+ # Supports back-navigation via +Ctrl+B+ and smart filtering via {SKIP_RULES}.
9
+ #
10
+ # @see CLI#run_interactive_wizard!
11
+ class Wizard
12
+ # Sentinel returned by the prompter when the user presses Ctrl+B.
13
+ BACK = Object.new.tap { |o| o.define_singleton_method(:inspect) { '#<BACK>' } }.freeze
14
+
15
+ # Human-readable labels for each option key.
16
+ #
17
+ # @return [Hash{Symbol => String}]
18
+ LABELS = {
19
+ api: 'API-only mode',
20
+ active_record: 'Active Record (ORM)',
21
+ database: 'Database',
22
+ javascript: 'JavaScript approach',
23
+ css: 'CSS framework',
24
+ asset_pipeline: 'Asset pipeline',
25
+ hotwire: 'Hotwire (Turbo + Stimulus)',
26
+ jbuilder: 'Jbuilder (JSON templates)',
27
+ action_mailer: 'Action Mailer',
28
+ action_mailbox: 'Action Mailbox',
29
+ action_text: 'Action Text (rich text)',
30
+ active_job: 'Active Job',
31
+ active_storage: 'Active Storage (file uploads)',
32
+ action_cable: 'Action Cable (WebSockets)',
33
+ test: 'Tests',
34
+ system_test: 'System tests',
35
+ brakeman: 'Brakeman (security scanner)',
36
+ bundler_audit: 'Bundler Audit (dependency checker)',
37
+ rubocop: 'RuboCop (linter)',
38
+ ci: 'CI files',
39
+ docker: 'Dockerfile',
40
+ kamal: 'Kamal (deployment)',
41
+ thruster: 'Thruster (HTTP/2 proxy)',
42
+ solid: 'Solid (Cache/Queue/Cable)',
43
+ devcontainer: 'Dev Container',
44
+ bootsnap: 'Bootsnap (boot speedup)',
45
+ dev_gems: 'Dev gems',
46
+ keeps: 'Source control .keep files',
47
+ decrypted_diffs: 'Decrypted diffs',
48
+ git: 'Initialize git',
49
+ bundle: 'Run bundle install'
50
+ }.freeze
51
+
52
+ # Short explanations shown below each wizard step.
53
+ #
54
+ # @return [Hash{Symbol => String}]
55
+ HELP_TEXT = {
56
+ api: 'Generates a slimmed-down app optimized for API backends.',
57
+ active_record: 'Database ORM layer. Skipping also skips the database choice.',
58
+ database: 'Which database adapter to configure.',
59
+ javascript: 'How JavaScript is managed in the asset pipeline.',
60
+ css: 'Which CSS framework to pre-install.',
61
+ asset_pipeline: 'Which asset pipeline to use for JS/CSS bundling.',
62
+ hotwire: 'Turbo + Stimulus for SPA-like behavior over HTML.',
63
+ jbuilder: 'DSL for building JSON views.',
64
+ action_mailer: 'Framework for sending emails.',
65
+ action_mailbox: 'Routes inbound emails to controller-like mailboxes.',
66
+ action_text: 'Rich text content and editing with Trix.',
67
+ active_job: 'Framework for declaring and running background jobs.',
68
+ active_storage: 'Upload files to cloud services like S3 or GCS.',
69
+ action_cable: 'WebSocket framework for real-time features.',
70
+ test: 'Generates test directory and helpers.',
71
+ system_test: 'Browser-based integration tests via Capybara.',
72
+ brakeman: 'Static analysis for security vulnerabilities.',
73
+ bundler_audit: 'Checks dependencies for known vulnerabilities.',
74
+ rubocop: 'Ruby style and lint checking.',
75
+ ci: 'Generates CI workflow configuration.',
76
+ docker: 'Generates Dockerfile for containerized deployment.',
77
+ kamal: 'Generates Kamal deploy configuration.',
78
+ thruster: 'HTTP/2 proxy with asset caching and X-Sendfile.',
79
+ solid: 'Solid Cache, Solid Queue, and Solid Cable adapters.',
80
+ devcontainer: 'Generates VS Code dev container configuration.',
81
+ bootsnap: 'Speeds up boot times with caching.',
82
+ dev_gems: 'Development gems like web-console.',
83
+ keeps: 'Empty directories preserved via .keep files.',
84
+ decrypted_diffs: 'Show decrypted diffs of encrypted credentials in git.',
85
+ git: 'Initializes a git repository for the new app.',
86
+ bundle: 'Runs bundle install after generating the app.'
87
+ }.freeze
88
+
89
+ # Per-choice hints displayed next to enum choices.
90
+ #
91
+ # @return [Hash{Symbol => Hash{String => String}}]
92
+ CHOICE_HELP = {
93
+ database: {
94
+ 'sqlite3' => 'simple file-based, great for development',
95
+ 'postgresql' => 'full-featured, most popular for production',
96
+ 'mysql' => 'widely used relational database',
97
+ 'trilogy' => 'modern MySQL-compatible client',
98
+ 'mariadb-mysql' => 'MariaDB with mysql2 adapter',
99
+ 'mariadb-trilogy' => 'MariaDB with Trilogy adapter'
100
+ },
101
+ javascript: {
102
+ 'importmap' => 'no bundler, uses browser-native import maps',
103
+ 'bun' => 'fast all-in-one JS runtime and bundler',
104
+ 'webpack' => 'established full-featured bundler',
105
+ 'esbuild' => 'extremely fast JS bundler',
106
+ 'rollup' => 'ES module-focused bundler',
107
+ 'none' => 'no JavaScript setup'
108
+ },
109
+ asset_pipeline: {
110
+ 'propshaft' => 'modern, lightweight asset pipeline',
111
+ 'sprockets' => 'classic asset pipeline with preprocessing',
112
+ 'none' => 'no asset pipeline'
113
+ },
114
+ css: {
115
+ 'tailwind' => 'utility-first CSS framework',
116
+ 'bootstrap' => 'popular component-based framework',
117
+ 'bulma' => 'modern CSS-only framework',
118
+ 'postcss' => 'CSS transformations via plugins',
119
+ 'sass' => 'CSS with variables, nesting, and mixins',
120
+ 'none' => 'no CSS framework'
121
+ }
122
+ }.freeze
123
+
124
+ # Rules that determine when a wizard step should be silently skipped.
125
+ # Each lambda receives the current values hash and returns true to skip.
126
+ #
127
+ # @return [Hash{Symbol => Proc}]
128
+ SKIP_RULES = {
129
+ database: ->(values) { values[:active_record] == false },
130
+ javascript: ->(values) { values[:api] == true },
131
+ css: ->(values) { values[:api] == true },
132
+ asset_pipeline: ->(values) { values[:api] == true },
133
+ hotwire: ->(values) { values[:api] == true },
134
+ jbuilder: ->(values) { values[:api] == true },
135
+ action_mailbox: ->(values) { values[:active_record] == false },
136
+ action_text: ->(values) { values[:api] == true || values[:active_record] == false },
137
+ active_storage: ->(values) { values[:active_record] == false },
138
+ system_test: ->(values) { values[:test] == false || values[:api] == true }
139
+ }.freeze
140
+
141
+ attr_reader :last_presented_index
142
+
143
+ # @param compatibility_entry [Compatibility::Matrix::Entry]
144
+ # @param defaults [Hash{Symbol => Object}] initial default values (e.g. last-used)
145
+ # @param prompter [UI::Prompter]
146
+ def initialize(compatibility_entry:, defaults:, prompter:)
147
+ @compatibility_entry = compatibility_entry
148
+ @prompter = prompter
149
+ @values = sanitize_defaults(defaults)
150
+ @stashed = {}
151
+ @last_presented_index = 0
152
+ end
153
+
154
+ # Runs the wizard and returns the selected options.
155
+ #
156
+ # @param start_index [Integer] step index to resume from
157
+ # @return [Hash{Symbol => Object}]
158
+ def run(start_index: 0)
159
+ keys = Options::Catalog::ORDER.select { |key| @compatibility_entry.supports_option?(key) }
160
+ index = [start_index, keys.length - 1].min
161
+ while index < keys.length
162
+ key = keys[index]
163
+
164
+ if skip_step?(key)
165
+ @stashed[key] = @values.delete(key) if @values.key?(key)
166
+ index += 1
167
+ next
168
+ end
169
+
170
+ @values[key] = @stashed.delete(key) if @stashed.key?(key) && !@values.key?(key)
171
+
172
+ @last_presented_index = index
173
+ answer = ask_for(key, index:, total: keys.length)
174
+ case answer
175
+ when BACK
176
+ index = find_previous_unskipped(keys, index)
177
+ else
178
+ assign_value(key, answer)
179
+ index += 1
180
+ end
181
+ end
182
+ @values.dup
183
+ end
184
+
185
+ private
186
+
187
+ # @param key [Symbol]
188
+ # @return [Boolean]
189
+ def skip_step?(key)
190
+ rule = SKIP_RULES[key]
191
+ rule&.call(@values)
192
+ end
193
+
194
+ # Finds the previous unskipped step index.
195
+ #
196
+ # @param keys [Array<Symbol>]
197
+ # @param current_index [Integer]
198
+ # @return [Integer]
199
+ def find_previous_unskipped(keys, current_index)
200
+ i = current_index - 1
201
+ # i.positive? (not i >= 0) stops the loop at i==0 so we can check
202
+ # the first step explicitly below. If every preceding step is
203
+ # skipped, we stay at current_index (nowhere to go back to).
204
+ i -= 1 while i.positive? && skip_step?(keys[i])
205
+ return current_index if i >= 0 && skip_step?(keys[i])
206
+
207
+ [i, 0].max
208
+ end
209
+
210
+ # @param key [Symbol]
211
+ # @param index [Integer]
212
+ # @param total [Integer]
213
+ # @return [Object] user answer or {BACK}
214
+ def ask_for(key, index:, total:)
215
+ definition = Options::Catalog.fetch(key)
216
+ label = LABELS.fetch(key)
217
+ skip_question = definition[:type] == :skip ||
218
+ (definition[:type] == :enum && !@compatibility_entry.allowed_values(key))
219
+ question = render_question(index: index, total: total, key: key, label: label, skip: skip_question)
220
+ case definition[:type]
221
+ when :skip
222
+ ask_skip(question, key)
223
+ when :flag
224
+ ask_flag(question, key)
225
+ when :enum
226
+ # When the Matrix provides nil (no enum values), the option is a
227
+ # simple include/skip for this Rails version (e.g. asset_pipeline in 8.0+).
228
+ if @compatibility_entry.allowed_values(key)
229
+ ask_enum(question, key, definition)
230
+ else
231
+ ask_skip(question, key)
232
+ end
233
+ else
234
+ raise Error, "Unknown option type for #{key}"
235
+ end
236
+ end
237
+
238
+ # @param question [String]
239
+ # @param key [Symbol]
240
+ # @return [true, false, BACK]
241
+ def ask_skip(question, key)
242
+ choices = %w[no yes]
243
+ current = @values[key]
244
+ selected = current == false ? 'yes' : 'no'
245
+ answer = choose_with_default_marker(question, key:, choices:, rails_default: 'no', selected:)
246
+ return BACK if answer == BACK
247
+ return false if answer == 'yes'
248
+
249
+ true
250
+ end
251
+
252
+ # @param question [String]
253
+ # @param key [Symbol]
254
+ # @return [true, nil, BACK]
255
+ def ask_flag(question, key)
256
+ choices = %w[yes no]
257
+ current = @values[key]
258
+ selected = current == true ? 'yes' : 'no'
259
+ answer = choose_with_default_marker(question, key:, choices:, rails_default: 'no', selected:)
260
+ return BACK if answer == BACK
261
+ return true if answer == 'yes'
262
+
263
+ nil
264
+ end
265
+
266
+ # @param question [String]
267
+ # @param key [Symbol]
268
+ # @param definition [Hash]
269
+ # @return [String, false, BACK]
270
+ def ask_enum(question, key, definition)
271
+ choices = @compatibility_entry.allowed_values(key).dup
272
+ choices << 'none' if definition[:none]
273
+ current = @values[key]
274
+ selected = enum_selected_choice(current, choices)
275
+ rails_default = definition[:rails_default] || choices.first
276
+ answer = choose_with_default_marker(question, key:, choices:, rails_default:, selected:)
277
+ return BACK if answer == BACK
278
+ return false if answer == 'none'
279
+
280
+ answer
281
+ end
282
+
283
+ # @param key [Symbol]
284
+ # @param value [Object]
285
+ # @return [void]
286
+ def assign_value(key, value)
287
+ if value.nil?
288
+ @values.delete(key)
289
+ else
290
+ @values[key] = value
291
+ end
292
+ end
293
+
294
+ # Filters defaults to only supported options.
295
+ #
296
+ # @param hash [Hash]
297
+ # @return [Hash{Symbol => Object}]
298
+ def sanitize_defaults(hash)
299
+ hash.transform_keys(&:to_sym)
300
+ .select { |key, _| @compatibility_entry.supports_option?(key) }
301
+ end
302
+
303
+ # Resolves which enum choice to pre-select based on the user's last pick.
304
+ #
305
+ # @param current [Object]
306
+ # @param choices [Array<String>]
307
+ # @return [String]
308
+ def enum_selected_choice(current, choices)
309
+ return 'none' if current == false && choices.include?('none')
310
+ return choices.first if current == true
311
+ return current if current.is_a?(String) && choices.include?(current)
312
+
313
+ choices.first
314
+ end
315
+
316
+ # Presents a choice list with the Rails default labeled and the user's
317
+ # last pick reordered first (pre-selected).
318
+ #
319
+ # @param question [String]
320
+ # @param key [Symbol]
321
+ # @param choices [Array<String>]
322
+ # @param rails_default [String] the true Rails default (labeled "(default)")
323
+ # @param selected [String] the user's last pick (reordered first)
324
+ # @return [String] the raw choice value, or {BACK}
325
+ def choose_with_default_marker(question, key:, choices:, rails_default:, selected:)
326
+ actual_selected = choices.include?(selected) ? selected : choices.first
327
+ ordered = choices.include?(rails_default) ? [rails_default] + (choices - [rails_default]) : choices
328
+ rendered_pairs = ordered.map do |choice|
329
+ [render_choice_label(key, choice, rails_default: rails_default), choice]
330
+ end
331
+ rendered = rendered_pairs.map(&:first)
332
+ selected_label = rendered_pairs.find { |_, raw| raw == actual_selected }&.first || rendered.first
333
+ answer = @prompter.choose(question, options: rendered, default: selected_label)
334
+ return BACK if answer == BACK
335
+
336
+ rendered_index = rendered.index(answer)
337
+ return ordered[rendered_index] if rendered_index
338
+
339
+ actual_selected
340
+ end
341
+
342
+ # @param index [Integer]
343
+ # @param total [Integer]
344
+ # @param key [Symbol]
345
+ # @param label [String]
346
+ # @return [String]
347
+ def render_question(index:, total:, key:, label:, skip: false)
348
+ step = format('%<current>02d/%<total>02d', current: index + 1, total: total)
349
+ if skip
350
+ "{{cyan:#{step}}} {{bold:Skip #{label}?}} - #{HELP_TEXT.fetch(key)}"
351
+ else
352
+ "{{cyan:#{step}}} {{bold:#{label}}} - #{HELP_TEXT.fetch(key)}"
353
+ end
354
+ end
355
+
356
+ # @param key [Symbol]
357
+ # @param choice [String]
358
+ # @param default_choice [String]
359
+ # @return [String]
360
+ def render_choice_label(key, choice, rails_default:)
361
+ label = choice
362
+ hint = CHOICE_HELP.dig(key, choice)
363
+ label = "#{label} - #{hint}" if hint
364
+ label = "#{label} (default)" if choice == rails_default
365
+ label
366
+ end
367
+ end
368
+ end
@@ -1,7 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'create_rails_app/version'
4
+ require_relative 'create_rails_app/error'
5
+ require_relative 'create_rails_app/detection/runtime'
6
+ require_relative 'create_rails_app/detection/rails_versions'
7
+ require_relative 'create_rails_app/options/catalog'
8
+ require_relative 'create_rails_app/compatibility/matrix'
9
+ require_relative 'create_rails_app/options/validator'
10
+ require_relative 'create_rails_app/command_builder'
11
+ require_relative 'create_rails_app/config/store'
12
+ require_relative 'create_rails_app/runner'
13
+ require_relative 'create_rails_app/ui/palette'
14
+ require_relative 'create_rails_app/ui/prompter'
15
+ require_relative 'create_rails_app/wizard'
16
+ require_relative 'create_rails_app/cli'
4
17
 
18
+ # Interactive TUI wizard for +rails new+.
19
+ #
20
+ # Detects installed Rails versions, shows version-aware options
21
+ # via a static compatibility matrix, and builds the correct +rails new+
22
+ # command. Config (presets, last-used options) is stored in
23
+ # +~/.config/create-rails-app/config.yml+.
24
+ #
25
+ # @see CreateRailsApp::CLI Entry point
26
+ # @see CreateRailsApp::Compatibility::Matrix Rails version compatibility
5
27
  module CreateRailsApp
6
- class Error < StandardError; end
7
28
  end
metadata CHANGED
@@ -1,16 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: create-rails-app
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leonid Svyatov
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
12
- description: Interactive CLI wizard for rails new. Walks you through every option,
13
- saves presets, and remembers your choices. Work in progress!
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: cli-kit
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: cli-ui
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.7'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.7'
40
+ description: Stop memorizing rails new flags. This interactive CLI wizard walks you
41
+ through every option, remembers your last choices, and saves reusable presets. Supports
42
+ Rails 7.2+ with version-aware option filtering, back navigation, and dry-run mode.
14
43
  email:
15
44
  - leonid@svyatov.com
16
45
  executables:
@@ -23,7 +52,20 @@ files:
23
52
  - README.md
24
53
  - exe/create-rails-app
25
54
  - lib/create_rails_app.rb
55
+ - lib/create_rails_app/cli.rb
56
+ - lib/create_rails_app/command_builder.rb
57
+ - lib/create_rails_app/compatibility/matrix.rb
58
+ - lib/create_rails_app/config/store.rb
59
+ - lib/create_rails_app/detection/rails_versions.rb
60
+ - lib/create_rails_app/detection/runtime.rb
61
+ - lib/create_rails_app/error.rb
62
+ - lib/create_rails_app/options/catalog.rb
63
+ - lib/create_rails_app/options/validator.rb
64
+ - lib/create_rails_app/runner.rb
65
+ - lib/create_rails_app/ui/palette.rb
66
+ - lib/create_rails_app/ui/prompter.rb
26
67
  - lib/create_rails_app/version.rb
68
+ - lib/create_rails_app/wizard.rb
27
69
  homepage: https://github.com/svyatov/create-rails-app
28
70
  licenses:
29
71
  - MIT
@@ -51,5 +93,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
51
93
  requirements: []
52
94
  rubygems_version: 4.0.6
53
95
  specification_version: 4
54
- summary: Interactive CLI wizard for rails new (coming soon)
96
+ summary: Interactive CLI wizard for rails new that remembers your choices and saves
97
+ presets.
55
98
  test_files: []