require "timeout" require "ember-cli/html_page" require "ember-cli/asset_resolver" module EmberCLI class App ADDON_VERSION = "0.0.13" EMBER_CLI_VERSIONS = [ "~> 0.1.5", "~> 0.2.0", "~> 1.13" ] class BuildError < StandardError; end attr_reader :name, :options, :paths, :pid delegate :root, to: :paths def initialize(name, **options) @name, @options = name.to_s, options @paths = PathSet.new(self) end def compile @compiled ||= begin prepare silence_build{ exec command } check_for_build_error! copy_index_html_file true end end def install_dependencies if gemfile_path.exist? exec "#{bundler_path} install" end exec "#{npm_path} prune && #{npm_path} install" if bower_path.nil? fail <<-FAIL Bower is required by EmberCLI. Install it with: $ npm install -g bower FAIL else exec "#{bower_path} prune && #{bower_path} install" end end def run prepare FileUtils.touch lockfile_path cmd = command(watch: true) @pid = exec(cmd, method: :spawn) Process.detach pid copy_index_html_file set_on_exit_callback end def run_tests prepare exit 1 unless exec("#{ember_path} test") end def stop Process.kill :INT, pid if pid @pid = nil end def index_html(sprockets:, head:, body:) asset_resolver = AssetResolver.new( app: self, sprockets: sprockets, ) html_page = HtmlPage.new( asset_resolver: asset_resolver, content: index_file.read, head: head, body: body, ) html_page.render end def exposed_js_assets [vendor_assets, application_assets] end alias exposed_css_assets exposed_js_assets def vendor_assets "#{name}/vendor" end def application_assets "#{name}/#{ember_app_name}" end def wait Timeout.timeout(build_timeout) do wait_for_build_complete_or_error end rescue Timeout::Error suggested_timeout = build_timeout + 5 warn <<-MSG.strip_heredoc ============================= WARNING! ============================= Seems like Ember #{name} application takes more than #{build_timeout} seconds to compile. To prevent race conditions consider adjusting build timeout configuration in your ember initializer: EmberCLI.configure do |config| config.build_timeout = #{suggested_timeout} # in seconds end Alternatively, you can set build timeout per application like this: EmberCLI.configure do |config| config.app :#{name}, build_timeout: #{suggested_timeout} end ============================= WARNING! ============================= MSG end def method_missing(method_name, *) if path_method = supported_path_method(method_name) paths.public_send(path_method) else super end end def respond_to_missing?(method_name, *) if supported_path_method(method_name) true else super end end private def set_on_exit_callback @on_exit_callback ||= at_exit{ stop } end def supported_path_method(original) path_method = original.to_s[/\A(.+)_path\z/, 1] path_method if path_method && paths.respond_to?(path_method) end def silence_build(&block) if ENV.fetch("EMBER_CLI_RAILS_VERBOSE"){ EmberCLI.env.production? } yield else silence_stream STDOUT, &block end end def build_timeout options.fetch(:build_timeout){ EmberCLI.configuration.build_timeout } end def watcher options.fetch(:watcher){ EmberCLI.configuration.watcher } end def check_for_build_error! raise_build_error! if build_error? end def reset_build_error! build_error_file_path.delete if build_error? end def build_error? build_error_file_path.exist? end def raise_build_error! error = BuildError.new("EmberCLI app #{name.inspect} has failed to build") error.set_backtrace build_error_file_path.read.split(?\n) fail error end def prepare @prepared ||= begin check_dependencies! check_addon! check_ember_cli_version! reset_build_error! symlink_to_assets_root add_assets_to_precompile_list true end end def check_ember_cli_version! version = dev_dependencies.fetch("ember-cli").split(?-).first unless Helpers.match_version?(version, EMBER_CLI_VERSIONS) fail <<-MSG.strip_heredoc EmberCLI Rails require ember-cli NPM package version to be #{EMBER_CLI_VERSIONS.last} to work properly (you have #{version}). From within your EmberCLI directory please update your package.json accordingly and run: $ npm install MSG end end def check_addon! unless addon_present? fail <<-MSG.strip_heredoc EmberCLI Rails requires your Ember app to have an addon. From within your EmberCLI directory please run: $ npm install --save-dev ember-cli-rails-addon@#{ADDON_VERSION} in your Ember application root: #{root} MSG end end def check_dependencies! unless node_modules_present? fail <<-MSG.strip_heredoc EmberCLI app dependencies are not installed. From your Rails application root please run: $ bundle exec rake ember:install If you do not require Ember at this URL, you can restrict this check using the `enable` option in the EmberCLI initializer. MSG end end def assets_path paths.assets.join(name) end def copy_index_html_file if environment == "production" FileUtils.cp(assets_path.join("index.html"), index_file) end end def index_file if environment == "production" applications_path.join("#{name}.html") else dist_path.join("index.html") end end def symlink_to_assets_root assets_path.make_symlink dist_path.join("assets") rescue Errno::EEXIST # Sometimes happens when starting multiple Unicorn workers. # Ignoring... end def add_assets_to_precompile_list Rails.configuration.assets.precompile << /\A#{name}\// end def command(watch: false) watch_flag = "" if watch watch_flag = "--watch" if watcher watch_flag += " --watcher #{watcher}" end end "#{ember_path} build #{watch_flag} --environment #{environment} --output-path #{dist_path} #{log_pipe}" end def log_pipe "| #{tee_path} -a #{log_path}" if tee_path end def ember_app_name @ember_app_name ||= options.fetch(:name){ package_json.fetch(:name) } end def environment EmberCLI.env.production?? "production" : "development" end def package_json @package_json ||= JSON.parse(package_json_file_path.read).with_indifferent_access end def addon_package_json @addon_package_json ||= JSON.parse(addon_package_json_file_path.read).with_indifferent_access end def addon_version addon_package_json.fetch("version") end def dev_dependencies package_json.fetch("devDependencies", {}) end def addon_present? addon_package_json_file_path.exist? && addon_version == ADDON_VERSION end def node_modules_present? node_modules_path.exist? end def excluded_ember_deps Array.wrap(options[:exclude_ember_deps]).join(?,) end def env_hash ENV.to_h.tap do |vars| vars["RAILS_ENV"] = Rails.env vars["DISABLE_FINGERPRINTING"] = "true" vars["EXCLUDE_EMBER_ASSETS"] = excluded_ember_deps vars["BUNDLE_GEMFILE"] = gemfile_path.to_s if gemfile_path.exist? end end def exec(cmd, method: :system) Dir.chdir root do Kernel.public_send(method, env_hash, cmd, err: :out) end end def wait_for_build_complete_or_error loop do check_for_build_error! break unless lockfile_path.exist? sleep 0.1 end end end end