diff --git a/.gitignore b/.gitignore index 74be847c..2d29fe59 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ test/dummy/tmp/ test/dummy/.sass-cache Gemfile.lock node_modules/ +dist/ +tmp/ diff --git a/Rakefile b/Rakefile index 7989ec19..2cf5afc9 100644 --- a/Rakefile +++ b/Rakefile @@ -8,6 +8,8 @@ require 'socket' require 'rake/testtask' require 'tmpdir' require 'securerandom' +require 'json' +require 'web_console/testing/erb_precompiler' EXPANDED_CWD = File.expand_path(File.dirname(__FILE__)) @@ -20,11 +22,11 @@ end namespace :test do desc "Run tests for templates" - task :templates => "templates:all" + task templates: "templates:all" namespace :templates do - task :all => [:daemonize, :npm, :rackup, :mocha, :kill] - task :serve => [:npm, :rackup] + task all: [ :daemonize, :npm, :rackup, :mocha, :kill ] + task serve: [ :npm, :rackup ] work_dir = Pathname(__FILE__).dirname.join("test/templates") pid_file = Pathname(Dir.tmpdir).join("web_console.#{SecureRandom.uuid}.pid") @@ -53,6 +55,67 @@ namespace :test do end end +namespace :ext do + rootdir = Pathname('extensions') + + desc 'Build Chrome Extension' + task chrome: 'chrome:build' + + namespace :chrome do + dist = Pathname('dist/crx') + extdir = rootdir.join(dist) + manifest_json = rootdir.join('chrome/manifest.json') + + directory extdir + + task build: [ extdir, 'lib:templates' ] do + cd rootdir do + cp_r [ 'img/', 'tmp/lib/' ], dist + `cd chrome && git ls-files`.split("\n").each do |src| + dest = dist.join(src) + mkdir_p dest.dirname + cp Pathname('chrome').join(src), dest + end + end + end + + # Generate a .crx file. + task crx: [ :build, :npm ] do + out = "crx-web-console-#{JSON.parse(File.read(manifest_json))["version"]}.crx" + cd(extdir) { sh "node \"$(npm bin)/crx\" pack ./ -p ../crx-web-console.pem -o ../#{out}" } + end + + # Generate a .zip file for Chrome Web Store. + task zip: [ :build ] do + version = JSON.parse(File.read(manifest_json))["version"] + cd(extdir) { sh "zip -r ../crx-web-console-#{version}.zip ./" } + end + + desc 'Launch a browser with the chrome extension.' + task run: [ :build ] do + cd(rootdir) { sh "sh ./script/run_chrome.sh --load-extension=#{dist}" } + end + end + + task :npm do + cd(rootdir) { sh "npm install --silent" } + end + + namespace :lib do + templates = Pathname('lib/web_console/templates') + tmplib = rootdir.join('tmp/lib/') + js_erb = FileList.new(templates.join('**/*.js.erb')) + dirs = js_erb.pathmap("%{^#{templates},#{tmplib}}d") + + task templates: dirs + js_erb.pathmap("%{^#{templates},#{tmplib}}X") + + dirs.each { |d| directory d } + rule '.js' => [ "%{^#{tmplib},#{templates}}X.js.erb" ] do |t| + File.write(t.name, WebConsole::Testing::ERBPrecompiler.new(t.source).build) + end + end +end + Bundler::GemHelper.install_tasks task default: :test diff --git a/extensions/README.markdown b/extensions/README.markdown new file mode 100644 index 00000000..4e8b2222 --- /dev/null +++ b/extensions/README.markdown @@ -0,0 +1,12 @@ +# Web Console Browser Extensions + +## Development + +### Quickstart + +``` +$ git clone https://github.com/rails/web-console.git +$ cd web-console +$ bundle install +$ bundle exec rake ext:chrome:run +``` diff --git a/extensions/package.json b/extensions/package.json new file mode 100644 index 00000000..4153ba23 --- /dev/null +++ b/extensions/package.json @@ -0,0 +1,7 @@ +{ + "name": "dev", + "version": "0.0.0", + "devDependencies": { + "crx": "^3.0.2" + } +} diff --git a/extensions/script/run_chrome.sh b/extensions/script/run_chrome.sh new file mode 100644 index 00000000..6704544b --- /dev/null +++ b/extensions/script/run_chrome.sh @@ -0,0 +1,24 @@ +#!/bin/sh + +is_command() { + type $1 > /dev/null 2>&1 +} + +find_chrome_binary() { + for name in 'chromium' 'google-chrome' 'chromium-browser'; do + if is_command $name; then + echo $name + return + fi + done +} + +CHROME_BINARY=${CHROME_BINARY:-`find_chrome_binary`} + +if is_command $CHROME_BINARY; then + $CHROME_BINARY $@ +else + echo 'ERROR: Chrome is not found.' + echo 'Please try "CHROME_BINARY=path/to/chrome".' + exit 1 +fi diff --git a/lib/web_console/testing/erb_precompiler.rb b/lib/web_console/testing/erb_precompiler.rb new file mode 100644 index 00000000..29cd050a --- /dev/null +++ b/lib/web_console/testing/erb_precompiler.rb @@ -0,0 +1,25 @@ +require 'web_console/testing/helper' +require 'web_console/testing/fake_middleware' + +module WebConsole + module Testing + # This class is to pre-compile 'templates/*.erb'. + class ERBPrecompiler + def initialize(path) + @erb = ERB.new(File.read(path)) + @view = FakeMiddleware.new( + view_path: Helper.gem_root.join('lib/web_console/templates'), + ).view + end + + def build + @erb.result(binding) + end + + def method_missing(name, *args, &block) + return super unless @view.respond_to?(name) + @view.send(name, *args, &block) + end + end + end +end diff --git a/lib/web_console/testing/fake_middleware.rb b/lib/web_console/testing/fake_middleware.rb new file mode 100644 index 00000000..b1a652f8 --- /dev/null +++ b/lib/web_console/testing/fake_middleware.rb @@ -0,0 +1,50 @@ +require 'action_view' +require 'action_dispatch' +require 'json' +require 'web_console/whitelist' +require 'web_console/request' + +module WebConsole + module Testing + class FakeMiddleware + DEFAULT_HEADERS = { "Content-Type" => "application/javascript" } + + def initialize(opts) + @headers = opts.fetch(:headers, DEFAULT_HEADERS) + @req_path_regex = opts[:req_path_regex] + @view_path = opts[:view_path] + end + + def call(env) + [ 200, @headers, [ render(req_path(env)) ] ] + end + + def view + @view ||= create_view + end + + private + + # extract target path from REQUEST_PATH + def req_path(env) + env["REQUEST_PATH"].match(@req_path_regex)[1] + end + + def render(template) + view.render(template: template, layout: nil) + end + + def create_view + lookup_context = ActionView::LookupContext.new(@view_path) + lookup_context.cache = false + FakeView.new(lookup_context) + end + + class FakeView < ActionView::Base + def render_inlined_string(template) + render(template: template, layout: "layouts/inlined_string") + end + end + end + end +end diff --git a/lib/web_console/testing/helper.rb b/lib/web_console/testing/helper.rb new file mode 100644 index 00000000..00a7fa66 --- /dev/null +++ b/lib/web_console/testing/helper.rb @@ -0,0 +1,9 @@ +module WebConsole + module Testing + module Helper + def self.gem_root + Pathname(File.expand_path('../../../../', __FILE__)) + end + end + end +end diff --git a/test/templates/config.ru b/test/templates/config.ru index e050197b..7fcdcb4c 100644 --- a/test/templates/config.ru +++ b/test/templates/config.ru @@ -1,55 +1,7 @@ -require "action_view" -require "pathname" -require "action_dispatch" -require "web_console" +require "web_console/testing/fake_middleware" TEST_ROOT = Pathname(__FILE__).dirname -class FakeMiddleware - DEFAULT_HEADERS = {"Content-Type" => "application/javascript"} - - def initialize(opts) - @headers = opts.fetch(:headers, DEFAULT_HEADERS) - @req_path_regex = opts[:req_path_regex] - @view_path = opts[:view_path] - end - - def call(env) - [200, headers, [render(req_path(env))]] - end - - private - - attr_reader :headers - attr_reader :req_path_regex - attr_reader :view_path - - # extract target path from REQUEST_PATH - def req_path(env) - env["REQUEST_PATH"].match(req_path_regex)[1] - end - - def render(template) - view.render(template: template, layout: nil) - end - - def view - @view ||= create_view - end - - def create_view - lookup_context = ActionView::LookupContext.new(view_path) - lookup_context.cache = false - FakeView.new(lookup_context) - end - - class FakeView < ActionView::Base - def render_inlined_string(template) - render(template: template, layout: "layouts/inlined_string") - end - end -end - # e.g. "/node_modules/mocha/mocha.js" map "/node_modules" do node_modules = TEST_ROOT.join("node_modules") @@ -61,7 +13,7 @@ end # test runners map "/html" do - run FakeMiddleware.new( + run WebConsole::Testing::FakeMiddleware.new( req_path_regex: %r{^/html/(.*)}, headers: {"Content-Type" => "text/html"}, view_path: TEST_ROOT.join("html"), @@ -69,14 +21,14 @@ map "/html" do end map "/spec" do - run FakeMiddleware.new( + run WebConsole::Testing::FakeMiddleware.new( req_path_regex: %r{^/spec/(.*)}, view_path: TEST_ROOT.join("spec"), ) end map "/templates" do - run FakeMiddleware.new( + run WebConsole::Testing::FakeMiddleware.new( req_path_regex: %r{^/templates/(.*)}, view_path: TEST_ROOT.join("../../lib/web_console/templates"), ) @@ -85,11 +37,11 @@ end map "/mock/repl/result" do headers = { 'Content-Type' => 'application/json' } body = [ { output: '=> "fake-result"\n' }.to_json ] - run lambda { |env| [200, headers, body] } + run lambda { |env| [ 200, headers, body ] } end map "/mock/repl/error" do headers = { 'Content-Type' => 'application/json' } body = [ { output: 'fake-error-message' }.to_json ] - run lambda { |env| [400, headers, body] } + run lambda { |env| [ 400, headers, body ] } end