rails-hyperstack 1.0.alpha1.2 → 1.0.alpha1.7

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.
@@ -6,11 +6,128 @@ module Rails
6
6
 
7
7
  protected
8
8
 
9
+ def warnings
10
+ @warnings ||= []
11
+ end
12
+
13
+ def create_component_file(template)
14
+ clear_cache
15
+ insure_hyperstack_loader_installed
16
+ check_javascript_link_directory
17
+ added = webpack_check
18
+ insure_base_component_class_exists
19
+ @no_help = options.key?('no-help')
20
+ self.components.each do |component|
21
+ component_array = component.split('::')
22
+ @modules = component_array[0..-2]
23
+ @file_name = component_array.last
24
+ @indent = 0
25
+ template template,
26
+ Rails.root.join('app', 'hyperstack', 'components',
27
+ *@modules.map(&:downcase),
28
+ "#{@file_name.underscore}.rb")
29
+ end
30
+ add_route
31
+ return unless added
32
+
33
+ say '📦 Webpack integrated with Hyperstack. '\
34
+ 'Add javascript assets to app/javascript/packs/client_only.js and /client_and_server.js 📦', :green
35
+ end
36
+
37
+
38
+ def clear_cache
39
+ run 'rm -rf tmp/cache' unless Dir.exist?(Rails.root.join('app', 'hyperstack'))
40
+ end
41
+
42
+ def insure_hyperstack_loader_installed
43
+ hyperstack_loader = %r{//=\s+require\s+hyperstack-loader\s+}
44
+ application_js = Rails.root.join(
45
+ 'app', 'assets', 'javascripts', 'application.js'
46
+ )
47
+ if File.exist? application_js
48
+ unless File.foreach(application_js).any? { |l| l =~ hyperstack_loader }
49
+ require_tree = %r{//=\s+require_tree\s+}
50
+ if File.foreach(application_js).any? { |l| l =~ require_tree }
51
+ inject_into_file 'app/assets/javascripts/application.js', verbose: false, before: require_tree do
52
+ "//= require hyperstack-loader\n"
53
+ end
54
+ else
55
+ warnings <<
56
+ " ***********************************************************\n"\
57
+ " * Could not add `//= require hyperstack-loader` directive *\n"\
58
+ " * to the app/assets/application.js file. *\n"\
59
+ " * Normally this directive is added just before the *\n"\
60
+ " * `//= require_tree .` directive at the end of the file, *\n"\
61
+ " * but no require_tree directive was found. You need to *\n"\
62
+ " * manually add `//= require hyperstack-loader` to the *\n"\
63
+ " * app/assets/application.js file. *\n"\
64
+ " ***********************************************************\n"
65
+ end
66
+ end
67
+ else
68
+ create_file application_js, "//= require hyperstack-loader\n"
69
+ warnings <<
70
+ " ***********************************************************\n"\
71
+ " * Could not find the app/assets/application.js file. *\n"\
72
+ " * We created one for you, and added the *\n"\
73
+ " * `<%= javascript_include_tag 'application' %>` to your *\n"\
74
+ " * `html.erb` files immediately after any *\n"\
75
+ " * `<%= javascript_pack 'application' %>` tags we found. *\n"\
76
+ " ***********************************************************\n"
77
+ application_pack_tag =
78
+ /\s*\<\%\=\s+javascript_pack_tag\s+(\'|\")application(\'|\").*\%\>.*$/
79
+ Dir.glob(Rails.root.join('app', 'views', '**', '*.erb')) do |file|
80
+ if File.foreach(file).any? { |l| l =~ application_pack_tag }
81
+ inject_into_file file, verbose: false, after: application_pack_tag do
82
+ "\n <%= javascript_include_tag 'application' %>"
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ def check_javascript_link_directory
90
+ manifest_js_file = Rails.root.join("app", "assets", "config", "manifest.js")
91
+ return unless File.exist? manifest_js_file
92
+ return unless File.readlines(manifest_js_file).grep(/javascripts \.js/).empty?
93
+
94
+ append_file manifest_js_file, "//= link_directory ../javascripts .js\n", verbose: false
95
+ end
96
+
97
+ def insure_base_component_class_exists
98
+ @component_base_class = options['base-class'] || Hyperstack.component_base_class
99
+ file_name = Rails.root.join(
100
+ 'app', 'hyperstack', 'components', "#{@component_base_class.underscore}.rb"
101
+ )
102
+ template 'hyper_component_template.rb', file_name unless File.exist? file_name
103
+ end
104
+
9
105
  def add_to_manifest(manifest, &block)
10
- if File.exists? "app/javascript/packs/#{manifest}"
11
- append_file "app/javascript/packs/#{manifest}", &block
106
+ if File.exist? "app/javascript/packs/#{manifest}"
107
+ append_file "app/javascript/packs/#{manifest}", verbose: false, &block
12
108
  else
13
- create_file "app/javascript/packs/#{manifest}", &block
109
+ create_file "app/javascript/packs/#{manifest}", verbose: false, &block
110
+ end
111
+ end
112
+
113
+ def add_route
114
+ return unless options['add-route']
115
+ if self.components.count > 1
116
+ warnings <<
117
+ " ***********************************************************\n"\
118
+ " * The add-route option ignored because more than one *\n"\
119
+ " * component is being generated. *\n"\
120
+ " ***********************************************************\n"
121
+ return
122
+ end
123
+ action_name = (@modules+[@file_name.underscore]).join('__')
124
+ path = options['add-route'] == 'add-route' ? '/(*others)' : options['add-route']
125
+ routing_code = "get '#{path}', to: 'hyperstack##{action_name}'\n"
126
+ log :route, routing_code
127
+ [/mount\s+Hyperstack::Engine[^\n]+\n/m, /\.routes\.draw do\s*\n/m].each do |sentinel|
128
+ in_root do
129
+ inject_into_file 'config/routes.rb', routing_code.indent(2), after: sentinel, verbose: false, force: false
130
+ end
14
131
  end
15
132
  end
16
133
 
@@ -18,6 +135,154 @@ module Rails
18
135
  return if system("yarn add #{package}#{'@' + version if version}")
19
136
  raise Thor::Error.new("yarn failed to install #{package} with version #{version}")
20
137
  end
138
+
139
+ def install_webpack
140
+ insure_yarn_loaded
141
+ add_webpacker_manifests
142
+ add_webpacks
143
+ cancel_react_source_import
144
+ install_webpacker
145
+ end
146
+
147
+ def inject_into_initializer(s)
148
+ file_name = Rails.root.join('config', 'initializers', 'hyperstack.rb')
149
+ if File.exist?(file_name)
150
+ prepend_to_file(file_name, verbose: false) { "#{s}\n" }
151
+ else
152
+ create_file file_name, <<-RUBY
153
+ #{s}
154
+
155
+ # server_side_auto_require will patch the ActiveSupport Dependencies module
156
+ # so that you can define classes and modules with files in both the
157
+ # app/hyperstack/xxx and app/xxx directories. For example you can split
158
+ # a Todo model into server and client related definitions and place this
159
+ # in `app/hyperstack/models/todo.rb`, and place any server only definitions in
160
+ # `app/models/todo.rb`.
161
+
162
+ require "hyperstack/server_side_auto_require.rb"
163
+
164
+ # set the component base class
165
+
166
+ Hyperstack.component_base_class = 'HyperComponent' # i.e. 'ApplicationComponent'
167
+
168
+ # prerendering is default :off, you should wait until your
169
+ # application is relatively well debugged before turning on.
170
+
171
+ Hyperstack.prerendering = :off # or :on
172
+
173
+ # add this line if you need jQuery AND ARE NOT USING WEBPACK
174
+ # Hyperstack.import 'hyperstack/component/jquery', client_only: true
175
+
176
+ # change definition of on_error to control how errors such as validation
177
+ # exceptions are reported on the server
178
+ module Hyperstack
179
+ def self.on_error(operation, err, params, formatted_error_message)
180
+ ::Rails.logger.debug(
181
+ "\#{formatted_error_message}\\n\\n" +
182
+ Pastel.new.red(
183
+ 'To further investigate you may want to add a debugging '\\
184
+ 'breakpoint to the on_error method in config/initializers/hyperstack.rb'
185
+ )
186
+ )
187
+ end
188
+ end if Rails.env.development?
189
+ RUBY
190
+ end
191
+ # whenever we modify the initializer its best to empty the cache, BUT
192
+ # we only need to it once per generator execution
193
+ run 'rm -rf tmp/cache' unless @cache_emptied_already
194
+ @cache_emptied_already = true
195
+ end
196
+
197
+ private
198
+
199
+ def webpack_check
200
+ return unless defined? ::Webpacker
201
+
202
+ client_and_server = Rails.root.join("app", "javascript", "packs", "client_only.js")
203
+ return if File.exist? client_and_server
204
+
205
+ # Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails hyperstack:install:webpack' }
206
+
207
+ # say "warning: you are running webpacker, but the hyperstack webpack files have not been created.\n"\
208
+ # " Suggest you run bundle exec rails hyperstack:install:webpack soon.\n"\
209
+ # " Or to avoid this warning create an empty file named app/javascript/packs/client_only.js",
210
+ # :red
211
+ install_webpack
212
+ true
213
+ end
214
+
215
+ def insure_yarn_loaded
216
+ begin
217
+ yarn_version = `yarn --version`
218
+ raise Errno::ENOENT if yarn_version.blank?
219
+ rescue Errno::ENOENT
220
+ raise Thor::Error.new("please insure nodejs is installed and the yarn command is available if using webpacker")
221
+ end
222
+ end
223
+
224
+ def add_webpacker_manifests
225
+ create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT
226
+ //app/javascript/packs/client_and_server.js
227
+ // these packages will be loaded both during prerendering and on the client
228
+ React = require('react'); // react-js library
229
+ createReactClass = require('create-react-class'); // backwards compatibility with ECMA5
230
+ History = require('history'); // react-router history library
231
+ ReactRouter = require('react-router'); // react-router js library
232
+ ReactRouterDOM = require('react-router-dom'); // react-router DOM interface
233
+ ReactRailsUJS = require('react_ujs'); // interface to react-rails
234
+ // to add additional NPM packages run `yarn add package-name@version`
235
+ // then add the require here.
236
+ JAVASCRIPT
237
+ create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT
238
+ //app/javascript/packs/client_only.js
239
+ // add any requires for packages that will run client side only
240
+ ReactDOM = require('react-dom'); // react-js client side code
241
+ jQuery = require('jquery'); // remove if you don't need jQuery
242
+ // to add additional NPM packages call run yarn add package-name@version
243
+ // then add the require here.
244
+ JAVASCRIPT
245
+ append_file 'config/initializers/assets.rb', verbose: false do
246
+ <<-RUBY
247
+ Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s
248
+ RUBY
249
+ end
250
+ inject_into_file 'config/environments/test.rb', verbose: false, before: /^end/ do
251
+ <<-RUBY
252
+
253
+ # added by hyperstack installer
254
+ config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s
255
+ RUBY
256
+ end
257
+ end
258
+
259
+ def add_webpacks
260
+ yarn 'react', '16'
261
+ yarn 'react-dom', '16'
262
+ yarn 'react-router', '^5.0.0'
263
+ yarn 'react-router-dom', '^5.0.0'
264
+ yarn 'react_ujs', '^2.5.0'
265
+ yarn 'jquery', '^3.4.1'
266
+ yarn 'create-react-class'
267
+ end
268
+
269
+ def cancel_react_source_import
270
+ inject_into_initializer(
271
+ "# Hyperstack.import 'react/react-source-browser' "\
272
+ "# uncomment this line if you want hyperstack to use its copy of react"
273
+ )
274
+ end
275
+
276
+ def install_webpacker
277
+ return if defined?(::Webpacker)
278
+
279
+ gem "webpacker"
280
+ Bundler.with_unbundled_env do
281
+ run "bundle install"
282
+ end
283
+ `spring stop`
284
+ Dir.chdir(Rails.root.join.to_s) { run 'bundle exec rails webpacker:install' }
285
+ end
21
286
  end
22
287
  end
23
288
  end
@@ -0,0 +1,229 @@
1
+ require_relative '../hyperstack/install_generator_base'
2
+ module Install
3
+ class HyperstackGenerator < Rails::Generators::Base
4
+
5
+ class_option 'skip-webpack', type: :boolean
6
+ class_option 'skip-hot-reloader', type: :boolean
7
+ class_option 'add-framework', type: :string
8
+
9
+ def insure_yarn_loaded
10
+ return if skip_webpack?
11
+ begin
12
+ yarn_version = `yarn --version`
13
+ raise Errno::ENOENT if yarn_version.blank?
14
+ rescue Errno::ENOENT
15
+ raise Thor::Error.new("please insure the yarn command is available if using webpacker")
16
+ end
17
+ end
18
+
19
+ APPJS = 'app/assets/javascripts/application.js'
20
+
21
+ def inject_hyperstack_loader_js
22
+ unless File.foreach(APPJS).any?{ |l| l['//= require hyperstack-loader'] }
23
+ inject_into_file 'app/assets/javascripts/application.js', before: %r{//= require_tree .} do
24
+ "//= require hyperstack-loader\n"
25
+ end
26
+ end
27
+ end
28
+
29
+ def create_hyperstack_files_and_directories
30
+ create_file 'app/hyperstack/components/hyper_component.rb', <<-RUBY
31
+ class HyperComponent
32
+ include Hyperstack::Component
33
+ include Hyperstack::State::Observable
34
+ param_accessor_style :accessors
35
+ end
36
+ RUBY
37
+ create_file 'app/hyperstack/operations/.keep', ''
38
+ create_file 'app/hyperstack/stores/.keep', ''
39
+ create_file 'app/hyperstack/models/.keep', ''
40
+ end
41
+
42
+ def move_and_update_application_record
43
+ unless File.exists? 'app/hyperstack/models/application_record.rb'
44
+ `mv app/models/application_record.rb app/hyperstack/models/application_record.rb`
45
+ create_file 'app/models/application_record.rb', <<-RUBY
46
+ # app/models/application_record.rb
47
+ # the presence of this file prevents rails migrations from recreating application_record.rb see https://github.com/rails/rails/issues/29407
48
+
49
+ require 'models/application_record.rb'
50
+ RUBY
51
+ end
52
+ end
53
+
54
+ def create_policies_directory
55
+ create_file 'app/policies/application_policy.rb', <<-RUBY
56
+ # app/policies/application_policy.rb
57
+
58
+ # Policies regulate access to your public models
59
+ # The following policy will open up full access (but only in development)
60
+ # The policy system is very flexible and powerful. See the documentation
61
+ # for complete details.
62
+ class Hyperstack::ApplicationPolicy
63
+ # Allow any session to connect:
64
+ always_allow_connection
65
+ # Send all attributes from all public models
66
+ regulate_all_broadcasts { |policy| policy.send_all }
67
+ # Allow all changes to models
68
+ allow_change(to: :all, on: [:create, :update, :destroy]) { true }
69
+ # allow remote access to all scopes - i.e. you can count or get a list of ids
70
+ # for any scope or relationship
71
+ ApplicationRecord.regulate_scope :all
72
+ end unless Rails.env.production?
73
+ RUBY
74
+ end
75
+
76
+ def add_router
77
+ generate "hyper:router", "App"
78
+ route "get '/(*other)', to: 'hyperstack#app'"
79
+ end
80
+ if false
81
+ def add_webpackin
82
+ run 'yarn add react'
83
+ run 'yarn add react-dom'
84
+ run 'yarn add react-router'
85
+ create_file 'app/javascript/packs/hyperstack.js', <<-CODE
86
+ // Import all the modules
87
+ import React from 'react';
88
+ import ReactDOM from 'react-dom';
89
+
90
+ // for opal/hyperstack modules to find React and others they must explicitly be saved
91
+ // to the global space, otherwise webpack will encapsulate them locally here
92
+ global.React = React;
93
+ global.ReactDOM = ReactDOM;
94
+ CODE
95
+ inject_into_file 'app/views/layouts/application.html.erb', before: %r{<%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>} do
96
+ <<-CODE
97
+ <%= javascript_pack_tag 'hyperstack' %>
98
+ CODE
99
+ end
100
+ gem 'webpacker'
101
+ end
102
+
103
+
104
+ else
105
+ def add_webpacker_manifests
106
+ return if skip_webpack?
107
+ create_file 'app/javascript/packs/client_and_server.js', <<-JAVASCRIPT
108
+ //app/javascript/packs/client_and_server.js
109
+ // these packages will be loaded both during prerendering and on the client
110
+ React = require('react'); // react-js library
111
+ History = require('history'); // react-router history library
112
+ ReactRouter = require('react-router'); // react-router js library
113
+ ReactRouterDOM = require('react-router-dom'); // react-router DOM interface
114
+ ReactRailsUJS = require('react_ujs'); // interface to react-rails
115
+ // to add additional NPM packages call run yarn add package-name@version
116
+ // then add the require here.
117
+ JAVASCRIPT
118
+ create_file 'app/javascript/packs/client_only.js', <<-JAVASCRIPT
119
+ //app/javascript/packs/client_only.js
120
+ // add any requires for packages that will run client side only
121
+ ReactDOM = require('react-dom'); // react-js client side code
122
+ jQuery = require('jquery');
123
+ // to add additional NPM packages call run yarn add package-name@version
124
+ // then add the require here.
125
+ JAVASCRIPT
126
+ append_file 'config/initializers/assets.rb' do
127
+ <<-RUBY
128
+ Rails.application.config.assets.paths << Rails.root.join('public', 'packs', 'js').to_s
129
+ RUBY
130
+ end
131
+ inject_into_file 'config/environments/test.rb', before: /^end/ do
132
+ <<-RUBY
133
+
134
+ # added by hyperstack installer
135
+ config.assets.paths << Rails.root.join('public', 'packs-test', 'js').to_s
136
+ RUBY
137
+ end
138
+
139
+ end
140
+
141
+ def add_webpacks
142
+ return if skip_webpack?
143
+ yarn 'react', '16'
144
+ yarn 'react-dom', '16'
145
+ yarn 'react-router', '^5.0.0'
146
+ yarn 'react-router-dom', '^5.0.0'
147
+ # yarn 'history'#, '4.2' this will be brought in by react-router
148
+ yarn 'react_ujs', '^2.5.0'
149
+ yarn 'jquery', '^3.4.1'
150
+ end
151
+
152
+ def add_webpacker_gem
153
+ gem 'webpacker'
154
+ end
155
+
156
+ def add_framework
157
+ framework = options['add-framework']
158
+ return unless framework
159
+ generate "hyperstack:install_#{framework}", "--no-build"
160
+ end
161
+
162
+ # def build_webpack
163
+ # system('bin/webpack')
164
+ # end
165
+ end
166
+ # all generators should be run before the initializer due to the opal-rails opal-jquery
167
+ # conflict
168
+
169
+ def create_initializer
170
+ create_file 'config/initializers/hyperstack.rb', <<-CODE
171
+ # config/initializers/hyperstack.rb
172
+ # If you are not using ActionCable, see http://hyperstack.orgs/docs/models/configuring-transport/
173
+ Hyperstack.configuration do |config|
174
+ config.transport = :action_cable
175
+ config.prerendering = :off # or :on
176
+ config.cancel_import 'react/react-source-browser' # bring your own React and ReactRouter via Yarn/Webpacker
177
+ config.import 'hyperstack/component/jquery', client_only: true # remove this line if you don't need jquery
178
+ config.import 'hyperstack/hotloader', client_only: true if Rails.env.development?
179
+ end
180
+
181
+ # useful for debugging
182
+ module Hyperstack
183
+ def self.on_error(operation, err, params, formatted_error_message)
184
+ ::Rails.logger.debug(
185
+ "\#{formatted_error_message}\\n\\n" +
186
+ Pastel.new.red(
187
+ 'To further investigate you may want to add a debugging '\\
188
+ 'breakpoint to the on_error method in config/initializers/hyperstack.rb'
189
+ )
190
+ )
191
+ end
192
+ end if Rails.env.development?
193
+ CODE
194
+ end
195
+
196
+ def inject_engine_to_routes
197
+ # this needs to be the first route, thus it must be the last method executed
198
+ route 'mount Hyperstack::Engine => \'/hyperstack\'' # this route should be first in the routes file so it always matches
199
+ end
200
+
201
+ def add_opal_hot_reloader
202
+ return if options['skip-hot-reloader']
203
+ create_file 'Procfile', <<-TEXT
204
+ web: bundle exec rails s -b 0.0.0.0
205
+ hot-loader: bundle exec hyperstack-hotloader -p 25222 -d app/hyperstack
206
+ TEXT
207
+ gem_group :development do
208
+ gem 'foreman'
209
+ end
210
+ end
211
+
212
+ def add_gems
213
+
214
+ end
215
+
216
+ def install
217
+ Bundler.with_unbundled_env do
218
+ run "bundle install"
219
+ end
220
+ run 'bundle exec rails webpacker:install'
221
+ end
222
+
223
+ private
224
+
225
+ def skip_webpack?
226
+ options['skip-webpack'] #|| !defined?(Webpacker)
227
+ end
228
+ end
229
+ end