Class: RailsAiContext::Introspectors::FrontendFrameworkIntrospector

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_ai_context/introspectors/frontend_framework_introspector.rb

Overview

Detects frontend frameworks, build tools, TypeScript config, monorepo layout, and component file counts from package.json, lockfiles, and bundler configs (Vite, Shakapacker, Webpacker).

Constant Summary collapse

MAX_PACKAGE_JSON_SIZE =

256 KB

256 * 1024
FRAMEWORK_MARKERS =
{
  "react" => :react, "react-dom" => :react,
  "next" => :nextjs,
  "vue" => :vue,
  "nuxt" => :nuxt,
  "@angular/core" => :angular,
  "svelte" => :svelte,
  "@sveltejs/kit" => :sveltekit,
  "react-native" => :react_native, "expo" => :expo,
  "solid-js" => :solid,
  "preact" => :preact
}.freeze
MOUNTING_MARKERS =
{
  "react_ujs" => :react_rails,
  "react-on-rails" => :react_on_rails,
  "@inertiajs/react" => :inertia,
  "@inertiajs/vue3" => :inertia,
  "@inertiajs/svelte" => :inertia,
  "vite-plugin-ruby" => :vite_rails,
  "vite-plugin-rails" => :vite_rails
}.freeze
STATE_MARKERS =
{
  "redux" => "Redux", "@reduxjs/toolkit" => "Redux Toolkit",
  "zustand" => "Zustand", "jotai" => "Jotai",
  "pinia" => "Pinia", "vuex" => "Vuex",
  "mobx" => "MobX", "@tanstack/react-query" => "TanStack Query"
}.freeze
TEST_MARKERS =
{
  "jest" => "Jest", "vitest" => "Vitest",
  "@playwright/test" => "Playwright", "cypress" => "Cypress",
  "@testing-library/react" => "Testing Library"
}.freeze
COMPONENT_EXTENSIONS =
%w[.jsx .tsx .vue .svelte].freeze
SCAN_SKIP_DIRS =
%w[node_modules dist build .next coverage __tests__].freeze
VITE_IMPORT_MARKERS =
{
  "vite-plugin-ruby" => :vite_rails,
  "@vitejs/plugin-react" => :react,
  "@vitejs/plugin-vue" => :vue,
  "@sveltejs/vite-plugin-svelte" => :svelte
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(app) ⇒ FrontendFrameworkIntrospector

Returns a new instance of FrontendFrameworkIntrospector.



63
64
65
# File 'lib/rails_ai_context/introspectors/frontend_framework_introspector.rb', line 63

def initialize(app)
  @app = app
end

Instance Attribute Details

#appObject (readonly)

Returns the value of attribute app.



12
13
14
# File 'lib/rails_ai_context/introspectors/frontend_framework_introspector.rb', line 12

def app
  @app
end

Instance Method Details

#callObject



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/rails_ai_context/introspectors/frontend_framework_introspector.rb', line 67

def call
  all_deps = read_package_json_deps
  frameworks = detect_frameworks(all_deps)
  mounting = detect_mounting_strategy(all_deps)
  state = detect_state_management(all_deps)
  testing = detect_testing(all_deps)
  pkg_mgr = detect_package_manager
  ts = detect_typescript
  mono = detect_monorepo(all_deps)
  build = detect_build_tool
  vite_fw = detect_vite_config_frameworks
  roots = detect_frontend_roots

  # Merge vite config detected frameworks into main frameworks hash
  vite_fw.each { |sym| frameworks[sym] ||= nil unless frameworks.key?(sym) }

  # Enrich each frontend root with component scan data
  enriched_roots = roots.map do |fr|
    full_path = File.join(root, fr[:path])
    counts = scan_components(full_path)
    primary_fw = frameworks.keys.first
    version = frameworks.values.compact.first
    fr.merge(
      framework: primary_fw,
      version: version,
      component_count: counts.values.sum,
      component_dirs: counts
    )
  end

  total_components = enriched_roots.sum { |r| r[:component_count] }

  {
    frontend_roots: enriched_roots,
    frameworks: frameworks,
    mounting_strategy: mounting,
    state_management: state,
    testing: testing,
    package_manager: pkg_mgr,
    typescript: ts,
    monorepo: mono,
    build_tool: build,
    api_clients: detect_api_clients(all_deps),
    component_libraries: detect_component_libraries(all_deps),
    summary: build_summary(frameworks, mounting, build, ts, total_components)
  }
rescue => e
  { error: e.message }
end