Class: PulseZero::Generators::InstallGenerator

Inherits:
Rails::Generators::Base
  • Object
show all
Defined in:
lib/generators/pulse_zero/install/install_generator.rb

Instance Method Summary collapse

Instance Method Details

#add_pulse_to_autoload_pathsObject



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 103

def add_pulse_to_autoload_paths
  # Add lib to autoload paths
  application_file = "config/application.rb"
  application_content = File.read(application_file)

  # Add to autoload_lib if not already there
  return if application_content.include?("config.autoload_lib")

  inject_into_file application_file, after: /class Application < Rails::Application\n/ do
    <<-RUBY
    # Autoload lib directory
    config.autoload_lib(ignore: %w[assets tasks])

    RUBY
  end
end

#check_prerequisitesObject



11
12
13
14
15
16
17
18
19
20
21
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 11

def check_prerequisites
  unless defined?(ActionCable)
    say "ActionCable is required. Installing...", :yellow
    generate "action_cable:install"
  end

  return if File.exist?("app/frontend")

  say "This generator is designed for Inertia.js applications with app/frontend directory", :red
  exit 1
end

#copy_backend_filesObject



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 39

def copy_backend_files
  # Core library files
  template "backend/lib/pulse.rb.tt", "lib/pulse.rb"
  template "backend/lib/pulse/engine.rb.tt", "lib/pulse/engine.rb"
  template "backend/lib/pulse/streams/broadcasts.rb.tt", "lib/pulse/streams/broadcasts.rb"
  template "backend/lib/pulse/streams/stream_name.rb.tt", "lib/pulse/streams/stream_name.rb"
  template "backend/lib/pulse/thread_debouncer.rb.tt", "lib/pulse/thread_debouncer.rb"

  # Application files
  template "backend/app/channels/pulse/channel.rb.tt", "app/channels/pulse/channel.rb"
  template "backend/app/controllers/concerns/pulse/request_id_tracking.rb.tt",
           "app/controllers/concerns/pulse/request_id_tracking.rb"
  template "backend/app/models/concerns/pulse/broadcastable.rb.tt",
           "app/models/concerns/pulse/broadcastable.rb"
  template "backend/app/jobs/pulse/broadcast_job.rb.tt", "app/jobs/pulse/broadcast_job.rb"

  # Configuration
  template "backend/config/initializers/pulse.rb.tt", "config/initializers/pulse.rb"
end

#copy_frontend_filesObject



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 59

def copy_frontend_files
  # TypeScript files in lib/pulse/
  template "frontend/lib/pulse.ts.tt", "app/frontend/lib/pulse/pulse.ts"
  template "frontend/lib/pulse-connection.ts.tt", "app/frontend/lib/pulse/pulse-connection.ts"
  template "frontend/lib/pulse-recovery-strategy.ts.tt", "app/frontend/lib/pulse/pulse-recovery-strategy.ts"
  template "frontend/lib/pulse-visibility-manager.ts.tt", "app/frontend/lib/pulse/pulse-visibility-manager.ts"

  # React hooks
  template "frontend/hooks/use-pulse.ts.tt", "app/frontend/hooks/use-pulse.ts"
  template "frontend/hooks/use-visibility-refresh.ts.tt", "app/frontend/hooks/use-visibility-refresh.ts"

  # Create or append to types/index.ts
  if File.exist?("app/frontend/types/index.ts")
    append_to_file "app/frontend/types/index.ts", pulse_types_content
  else
    create_file "app/frontend/types/index.ts", pulse_types_content
  end
end

#create_documentationObject



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 131

def create_documentation
  template "docs/PULSE_USAGE.md.tt", "docs/PULSE_USAGE.md"

  say "\n✅ Pulse Zero has been installed!", :green
  say "🚀 Real-time broadcasting system generated with zero runtime dependencies", :blue
  say "\n⚠️  IMPORTANT: Configure authentication!", :yellow
  say "The default ApplicationCable connection accepts all connections."
  say "Edit app/channels/application_cable/connection.rb to add your authentication logic."
  say "\nWhat was generated:", :yellow
  say "• Backend: Broadcasting system in lib/pulse/, models, controllers, channels, and jobs"
  say "• Frontend: TypeScript WebSocket management and React hooks"
  say "• Security: Signed streams for secure channel subscriptions"
  say "• Features: Auto-reconnection, tab suspension handling, and more"
  say "\nQuick start:", :blue
  say <<~EXAMPLE
    # 1. Enable broadcasting on a model:
    class Post < ApplicationRecord
      include Pulse::Broadcastable
      broadcasts_to ->(post) { "posts" }
    end

    # 2. Pass stream token to frontend:
    render inertia: "Post/Index", props: {
      posts: @posts,
      pulseStream: Pulse::Streams::StreamName.signed_stream_name("posts")
    }

    # 3. Subscribe in React component:
    usePulse(pulseStream, (message) => {
      switch (message.event) {
        case 'created':
          setPosts(prev => [message.payload, ...prev])
          break
        case 'updated':
          setPosts(prev => 
            prev.map(post => post.id === message.payload.id ? message.payload : post)
          )
          break
        case 'deleted':
          setPosts(prev => prev.filter(post => post.id !== message.payload.id))
          break
        case 'refresh':
          router.reload()
          break
      }
    })
  EXAMPLE
  say "\nNext steps:", :yellow
  say "1. Configure authentication in app/channels/application_cable/connection.rb"
  say "2. Read docs/PULSE_USAGE.md for complete documentation"
  say "3. Enable debug logging: localStorage.setItem('PULSE_DEBUG', 'true')"
  say "\nInspired by Turbo Rails • Full ownership of generated code • Customize everything!"
end

#install_npm_dependenciesObject



96
97
98
99
100
101
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 96

def install_npm_dependencies
  return unless File.exist?("package.json")

  say "Installing @rails/actioncable...", :green
  run "npm install @rails/actioncable"
end

#setup_action_cableObject



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 23

def setup_action_cable
  # Generate base channel classes if missing
  unless File.exist?("app/channels/application_cable/connection.rb")
    template "backend/app/channels/application_cable/connection.rb.tt",
             "app/channels/application_cable/connection.rb"
  end

  unless File.exist?("app/channels/application_cable/channel.rb")
    template "backend/app/channels/application_cable/channel.rb.tt",
             "app/channels/application_cable/channel.rb"
  end

  # Add route if missing
  route 'mount ActionCable.server => "/cable"' unless action_cable_route_exists?
end

#setup_application_controllerObject



120
121
122
123
124
125
126
127
128
129
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 120

def setup_application_controller
  # Check if already included
  controller_content = File.read("app/controllers/application_controller.rb")
  return if controller_content.include?("Pulse::RequestIdTracking")

  inject_into_class "app/controllers/application_controller.rb",
                    "ApplicationController" do
    "  include Pulse::RequestIdTracking\n"
  end
end

#setup_autoload_pathsObject



92
93
94
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 92

def setup_autoload_paths
  add_pulse_to_autoload_paths
end

#setup_current_modelObject



78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/generators/pulse_zero/install/install_generator.rb', line 78

def setup_current_model
  if File.exist?("app/models/current.rb")
    # Check if pulse_request_id is already defined
    current_content = File.read("app/models/current.rb")
    unless current_content.include?("pulse_request_id")
      inject_into_class "app/models/current.rb", "Current" do
        "  attribute :pulse_request_id\n"
      end
    end
  else
    template "backend/app/models/current.rb.tt", "app/models/current.rb"
  end
end