Module: Ruact::Generators::ScaffoldGenerator::ShadcnPreflight

Included in:
Ruact::Generators::ScaffoldGenerator
Defined in:
lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb

Overview

Story 10.5 — the shadcn/ui dependency pre-flight's read-only helpers: the authoritative add-list (derived from the templates' own import predicates), state detection (complete / missing / partial), the copy-pasteable guidance messages, and the best-effort version-compat read + warning. Extracted into its own module (mirrors the 10.3 FormHelpers / 10.2 ScaffoldAttribute extractions) to keep Ruact::Generators::ScaffoldGenerator within its class-length budget. Mixed in below the no_tasks boundary so these never register as Thor commands — only the public check_shadcn_setup task (in the generator) is a command.

Constant Summary collapse

SHADCN_DOCS_POINTER =

Documentation anchor for the shadcn dependency pre-flight: how to set up shadcn, and how to override the version-compat warning.

"https://github.com/luizcg/ruact/blob/main/website/docs/api/scaffold.md#shadcnui-setup"

Instance Method Summary collapse

Instance Method Details

#compute_shadcn_setup_stateObject



93
94
95
96
97
98
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 93

def compute_shadcn_setup_state
  return :missing unless shadcn_config_path.exist?
  return :complete if missing_shadcn_components.empty?

  :partial
end

#host_package_jsonObject

AC6 — the host package.json, parsed best-effort (nil when absent or unparseable; a malformed file must not crash the generator).



163
164
165
166
167
168
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 163

def host_package_json
  return @host_package_json if defined?(@host_package_json)

  path = Pathname(destination_root).join("package.json")
  @host_package_json = path.exist? ? safe_parse_json(path.read) : nil
end

#installed_shadcn_majorObject

The MAJOR of the installed version ("^2.1.0" → 2, ">=1.0" → 1).



194
195
196
197
198
199
200
201
202
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 194

def installed_shadcn_major
  raw = shadcn_version_string
  return nil unless raw

  digits = raw.to_s.gsub(/[^0-9.]/, "").split(".").first
  return nil if digits.nil? || digits.empty?

  digits.to_i
end

#missing_shadcn_componentsObject

The required primitives whose ui/<name>.tsx file is absent (drives the partial-setup "lists exactly which components are missing" guidance).



76
77
78
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 76

def missing_shadcn_components
  required_shadcn_components.reject { |name| shadcn_component_file(name).exist? }
end

#missing_shadcn_messageObject

AC2 — missing setup: the full init + single add <full list> sequence (all plain adds; NO data-table recipe, NO @tanstack install line).



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 130

def missing_shadcn_message
  <<~MSG.chomp
    ruact:scaffold — shadcn/ui is not set up in this app yet.
    The generated components import from @/components/ui/*, which does not exist.
    Set up shadcn/ui first, then re-run this generator:

      npx shadcn@latest init
      #{shadcn_add_command(required_shadcn_components)}

    No files were written (no partial state). Advanced: pass --skip-shadcn-check
    to generate anyway and manage @/components/ui/* yourself.
  MSG
end

#partial_shadcn_messageObject

AC4 — partial setup: the EXACT missing names + the targeted add (only the missing pieces). Never auto-run.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 146

def partial_shadcn_message
  missing = missing_shadcn_components
  <<~MSG.chomp
    ruact:scaffold — shadcn/ui is configured, but components the scaffold imports
    are not installed yet:
      #{missing.join(', ')}
    Add them, then re-run this generator:

      #{shadcn_add_command(missing)}

    No files were written (no partial state). Advanced: pass --skip-shadcn-check
    to generate anyway and manage @/components/ui/* yourself.
  MSG
end

#required_shadcn_componentsObject

AC1/AC2/AC4/AC7 — the authoritative @/components/ui/* primitives the generated output imports, DERIVED from the templates' own predicates (the FormHelpers form_uses_*?) so it can never drift from the emitted import set. Ordered to match the documented single add line: button + the form-conditional input-family, then the always-present List/DeleteDialog primitives. EVERY entry is a plain npx shadcn add primitive — there is NO data-table recipe and NO @tanstack/react-table dependency (Story 10.2b removed the engine; the List is a plain table).



50
51
52
53
54
55
56
57
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 50

def required_shadcn_components
  components = ["button"]
  components << "input"    if form_uses_input?
  components << "textarea" if form_uses_textarea?
  components << "switch"   if form_uses_switch?
  components << "select"   if form_uses_select?
  components.push("label", "badge", "table", "alert-dialog", "dropdown-menu")
end

#run_shadcn_preflight!Object

The pre-flight body (the Ruact::Generators::ScaffoldGenerator#check_shadcn_setup Thor command delegates here). Detect the host's shadcn state, surface the version-compat warning, and ABORT before any write (via raise Thor::Error, like assert_supported_attribute_types!) when the setup is missing/partial — unless --skip-shadcn-check.

Raises:

  • (Thor::Error)


30
31
32
33
34
35
36
37
38
39
40
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 30

def run_shadcn_preflight!
  warn_incompatible_shadcn_version

  state = shadcn_setup_state
  return if state == :complete
  # --skip-shadcn-check writes anyway; detection still ran so the
  # templates can emit the in-file banner (see {#shadcn_missing?}).
  return if skip_shadcn_check?

  raise Thor::Error, state == :missing ? missing_shadcn_message : partial_shadcn_message
end

#safe_parse_json(raw) ⇒ Object



170
171
172
173
174
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 170

def safe_parse_json(raw)
  JSON.parse(raw)
rescue JSON::ParserError
  nil
end

#shadcn_add_command(components) ⇒ Object

The single copy-pasteable add line for the given component list.



114
115
116
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 114

def shadcn_add_command(components)
  "npx shadcn@latest add #{components.join(' ')}"
end

#shadcn_banner_add_commandObject

AC3 — the add command for the in-file banner. Uses the detectably missing primitives, falling back to the full required list when none is missing (e.g. the ui/* files exist but components.json is absent → state :missing) so the banner NEVER prints a bare npx shadcn add.



122
123
124
125
126
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 122

def shadcn_banner_add_command
  components = missing_shadcn_components
  components = required_shadcn_components if components.empty?
  shadcn_add_command(components)
end

#shadcn_component_file(name) ⇒ Object



70
71
72
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 70

def shadcn_component_file(name)
  shadcn_ui_dir.join("#{name}.tsx")
end

#shadcn_config_pathObject



66
67
68
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 66

def shadcn_config_path
  Pathname(destination_root).join("components.json")
end

#shadcn_missing?Boolean

AC3 — true only when the developer bypassed the pre-flight with --skip-shadcn-check AND the setup is still incomplete, so the components emit the prominent in-file banner. Default/configured runs leave this false → the templates' default-path bytes are unchanged (the 10.1–10.4/10.2b tsc byte-equality fixtures stay valid without regen).

Returns:

  • (Boolean)


109
110
111
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 109

def shadcn_missing?
  skip_shadcn_check? && shadcn_setup_state != :complete
end

#shadcn_setup_stateObject

Detection (memoized — the FS does not change during a run):

:complete = components.json present AND every required ui/<name>.tsx present
:missing  = no components.json (shadcn is not initialized) → guide to
          `init` + the full `add`. A `components.json` is the
          authoritative "shadcn is set up" marker; without it the
          setup is incomplete even if a `components/ui/` dir exists
          (so we never emit a bare/empty `add` for a config-less app).
:partial  = components.json present but some required primitive absent
          → targeted `add` of exactly the missing pieces.


89
90
91
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 89

def shadcn_setup_state
  @shadcn_setup_state ||= compute_shadcn_setup_state
end

#shadcn_ui_dirObject

The app/javascript/components/ui/ directory the @/components/ui/* alias maps to (consistent with ruact:install's app/javascript convention), and shadcn's components.json config marker at app root.



62
63
64
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 62

def shadcn_ui_dir
  Pathname(destination_root).join("app/javascript/components/ui")
end

#shadcn_version_stringObject

AC6 — best-effort installed shadcn version string: shadcn (current) or legacy shadcn-ui, from dependencies/devDependencies. nil when run via npx (no pin) → a soft note, not a warning.



179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 179

def shadcn_version_string
  pkg = host_package_json
  return nil unless pkg.is_a?(Hash)

  %w[dependencies devDependencies].each do |section|
    deps = pkg[section]
    next unless deps.is_a?(Hash)

    raw = deps["shadcn"] || deps["shadcn-ui"]
    return raw if raw
  end
  nil
end

#skip_shadcn_check?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 100

def skip_shadcn_check?
  options[:skip_shadcn_check]
end

#warn_incompatible_shadcn_versionObject

AC6 — warn (never abort) when the installed shadcn major is outside the tested set; a soft note when the version can't be determined.



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/generators/ruact/scaffold/scaffold_shadcn_preflight.rb', line 206

def warn_incompatible_shadcn_version
  major = installed_shadcn_major
  if major.nil?
    say_status "note",
               "could not determine the installed shadcn version (run via npx?); " \
               "skipping the version-compat check", :yellow
    return
  end

  compatible = Ruact.config.shadcn_compatible_versions
  return if compatible.include?(major)

  say_status "warning",
             "shadcn v#{major} is not regression-tested with this ruact " \
             "(tested majors: #{compatible.join(', ')}); the generated code may import " \
             "from outdated @/components/ui/* paths. Align shadcn to a tested major, or add " \
             "#{major} via Ruact.configure { |c| c.shadcn_compatible_versions = " \
             "#{(compatible + [major]).sort.inspect} } in config/initializers/ruact.rb once " \
             "verified. See #{SHADCN_DOCS_POINTER}", :yellow
end