From bd0286e8d399a5915e7f3ca49546ceca769aa737 Mon Sep 17 00:00:00 2001 From: Likun Liu Date: Sun, 16 Mar 2014 23:01:00 +1100 Subject: [PATCH 01/27] support for commonjs modules --- .../assets/require.js.erb | 28 +++++++++++++++++++ lib/zendesk_apps_support/package.rb | 24 ++++++++++++++-- .../validations/source.rb | 18 ++++++++---- spec/validations/source_spec.rb | 2 +- 4 files changed, 63 insertions(+), 9 deletions(-) create mode 100644 lib/zendesk_apps_support/assets/require.js.erb diff --git a/lib/zendesk_apps_support/assets/require.js.erb b/lib/zendesk_apps_support/assets/require.js.erb new file mode 100644 index 00000000..42cf7863 --- /dev/null +++ b/lib/zendesk_apps_support/assets/require.js.erb @@ -0,0 +1,28 @@ +var require = (function() { + var modules = {}, cache = {}; + + var require = function(path) { + var module = cache[path]; + if (module) { + return module; + } else if (modules[path]) { + module = {exports: {}}; + modules[path].call(null, require, module); + cache[path] = module.exports; + return cache[path]; + } else { + throw 'module ' + path + ' not found'; + } + }; + + modules = { + <% modules.each do |path, content| %> + '<%= path %>': function(require, module) { +<%= content %> + }, + <% end %> + eom: undefined + }; + + return require; +})(); \ No newline at end of file diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index 45f5b0ad..e114cbdb 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -9,6 +9,7 @@ class Package DEFAULT_LAYOUT = Erubis::Eruby.new( File.read(File.expand_path('../assets/default_template.html.erb', __FILE__)) ) DEFAULT_SCSS = File.read(File.expand_path('../assets/default_styles.scss', __FILE__)) SRC_TEMPLATE = Erubis::Eruby.new( File.read(File.expand_path('../assets/src.js.erb', __FILE__)) ) + MODULES_TEMPLATE = Erubis::Eruby.new( File.read(File.expand_path('../assets/require.js.erb', __FILE__)) ) attr_reader :root, :warnings attr_accessor :requirements_only @@ -42,6 +43,22 @@ def validate end end + def compiled_js + return read_file("app.js") unless has_lib_js? + + modules = {} + Dir["#{@root}/lib/*.js"].each do |file| + name = File.basename(file) + content = File.read(file) + modules["lib/#{name}"] = content + end + + insert = MODULES_TEMPLATE.result(:modules => modules) + original = read_file("app.js") + + original.sub(/^\s*\(\s*function\s*\(\s*\)\s*\{/, "(function() {\n#{insert}\n") + end + def files non_tmp_files end @@ -72,7 +89,7 @@ def app_translations def readified_js(app_name, app_id, asset_url_prefix, settings={}) manifest = manifest_json - source = read_file("app.js") + source = compiled_js name = app_name || manifest[:name] || 'Local App' location = manifest[:location] app_class_name = "app-#{app_id}" @@ -106,6 +123,10 @@ def has_js? file_exists?("app.js") end + def has_lib_js? + Dir["#{@root}/lib/*.js"].any? + end + def has_manifest? file_exists?("manifest.json") end @@ -155,7 +176,6 @@ def file_exists?(path) end def read_file(path) - file_path = File.join(root, path) File.read(File.join(root, path)) end diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index a6fad4d0..40994727 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -14,7 +14,7 @@ module Source :laxcomma => true, # predefined globals: - :predef => %w(_ console services helpers alert JSON Base64 clearInterval clearTimeout setInterval setTimeout) + :predef => %w(_ console services helpers alert JSON Base64 clearInterval clearTimeout setInterval setTimeout require module) }.freeze class < 'app.js', :read => "var a = 1") - package = mock('Package', :files => [source], :requirements_only => false) + package = mock('Package', :root => '.', :files => [source], :requirements_only => false) errors = ZendeskAppsSupport::Validations::Source.call(package) errors.first.to_s.should eql "JSHint error in app.js: \n L1: Missing semicolon." From 2651a28d1c0337a3200251417e6ba17a23bc7bb6 Mon Sep 17 00:00:00 2001 From: Likun Liu Date: Mon, 17 Mar 2014 11:23:03 +1100 Subject: [PATCH 02/27] tests for commonjs compilation and validations. --- .../assets/require.js.erb | 2 +- lib/zendesk_apps_support/package.rb | 2 +- spec/app/app.js | 3 ++ spec/app/lib/a.js | 5 +++ spec/package_spec.rb | 37 ++++++++++++++++++- spec/validations/source_spec.rb | 9 +++++ 6 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 spec/app/lib/a.js diff --git a/lib/zendesk_apps_support/assets/require.js.erb b/lib/zendesk_apps_support/assets/require.js.erb index 42cf7863..0900a004 100644 --- a/lib/zendesk_apps_support/assets/require.js.erb +++ b/lib/zendesk_apps_support/assets/require.js.erb @@ -25,4 +25,4 @@ var require = (function() { }; return require; -})(); \ No newline at end of file +})(); diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index e114cbdb..9119d070 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -56,7 +56,7 @@ def compiled_js insert = MODULES_TEMPLATE.result(:modules => modules) original = read_file("app.js") - original.sub(/^\s*\(\s*function\s*\(\s*\)\s*\{/, "(function() {\n#{insert}\n") + original.sub(/^\s*\(\s*function\s*\(\s*\)\s*\{/, "(function() {\n#{insert}") end def files diff --git a/spec/app/app.js b/spec/app/app.js index 63ad2677..8c31b6cb 100644 --- a/spec/app/app.js +++ b/spec/app/app.js @@ -1,11 +1,14 @@ (function() { return { + a: require('lib/a.js'), + events: { 'app.activated':'doSomething' }, doSomething: function() { + console.log(a.name); } }; diff --git a/spec/app/lib/a.js b/spec/app/lib/a.js new file mode 100644 index 00000000..e41a19f0 --- /dev/null +++ b/spec/app/lib/a.js @@ -0,0 +1,5 @@ +var a = { + name: 'This is A' +}; + +module.exports = a; diff --git a/spec/package_spec.rb b/spec/package_spec.rb index 95e26288..d93da45f 100644 --- a/spec/package_spec.rb +++ b/spec/package_spec.rb @@ -7,7 +7,7 @@ describe 'files' do it 'should return all the files within the app folder excluding files in tmp folder' do - @package.files.map(&:relative_path).should =~ %w(app.css app.js assets/logo-small.png assets/logo.png manifest.json templates/layout.hdbs translations/en.json) + @package.files.map(&:relative_path).should =~ %w(app.css app.js assets/logo-small.png assets/logo.png lib/a.js manifest.json templates/layout.hdbs translations/en.json) end it 'should error out when manifest is missing' do @@ -51,13 +51,48 @@ with( require('apps/framework/app_scope') ) { var source = (function() { +var require = (function() { + var modules = {}, cache = {}; + + var require = function(path) { + var module = cache[path]; + if (module) { + return module; + } else if (modules[path]) { + module = {exports: {}}; + modules[path].call(null, require, module); + cache[path] = module.exports; + return cache[path]; + } else { + throw 'module ' + path + ' not found'; + } + }; + + modules = { + 'lib/a.js': function(require, module) { +var a = { + name: 'This is A' +}; + +module.exports = a; + + }, + eom: undefined + }; + + return require; +})(); + return { + a: require('lib/a.js'), + events: { 'app.activated':'doSomething' }, doSomething: function() { + console.log(a.name); } }; diff --git a/spec/validations/source_spec.rb b/spec/validations/source_spec.rb index 71f42e8e..cffb7592 100644 --- a/spec/validations/source_spec.rb +++ b/spec/validations/source_spec.rb @@ -38,4 +38,13 @@ errors.first.to_s.should eql "JSHint error in app.js: \n L1: Missing semicolon." end + it 'should have a jslint error when missing semicolon in lib js file' do + source = mock('AppFile', :relative_path => 'app.js', :read => "") + package = mock('Package', :root => '.', :files => [source], :requirements_only => false) + Dir.stub('[]').with('./lib/*.js').and_return(['a.js']) + File.stub(:read).with('a.js').and_return("var a = 1") + errors = ZendeskAppsSupport::Validations::Source.call(package) + + errors.first.to_s.should eql "JSHint error in a.js: \n L1: Missing semicolon." + end end From 4f5d2977ecb87ec1918c77c18c52bbb3e093a3ff Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Tue, 18 Mar 2014 12:42:20 +1100 Subject: [PATCH 03/27] Allow globbing to find nested libs --- lib/zendesk_apps_support/package.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index 9119d070..6c34e21f 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -47,10 +47,11 @@ def compiled_js return read_file("app.js") unless has_lib_js? modules = {} - Dir["#{@root}/lib/*.js"].each do |file| - name = File.basename(file) - content = File.read(file) - modules["lib/#{name}"] = content + Dir["#{@root}/lib/**/*.js"].each do |file| + next if File.symlink?(file) + name = Pathname.new(file).relative_path_from(@root) + content = File.read(file) + modules[name] = content end insert = MODULES_TEMPLATE.result(:modules => modules) From 0e33eb29e3007ac97f1b081965fc239c681d27a7 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 21 Mar 2014 10:45:36 +1100 Subject: [PATCH 04/27] Update all lib paths to check for subdirectories --- lib/zendesk_apps_support/package.rb | 9 ++++++--- lib/zendesk_apps_support/validations/source.rb | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index 6c34e21f..368d04dd 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -47,8 +47,7 @@ def compiled_js return read_file("app.js") unless has_lib_js? modules = {} - Dir["#{@root}/lib/**/*.js"].each do |file| - next if File.symlink?(file) + lib_files.each do |file| name = Pathname.new(file).relative_path_from(@root) content = File.read(file) modules[name] = content @@ -64,6 +63,10 @@ def files non_tmp_files end + def lib_files + @lib_files ||= Dir["#{@root}/lib/**/*.js"] + end + def template_files files.select { |f| f =~ /^templates\/.*\.hdbs$/ } end @@ -125,7 +128,7 @@ def has_js? end def has_lib_js? - Dir["#{@root}/lib/*.js"].any? + lib_files.any? end def has_manifest? diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index 40994727..f239e11d 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -32,7 +32,7 @@ def call(package) if app_js_errors.any? jshint_errors += [ JSHintValidationError.new(source.relative_path, app_js_errors) ] end - Dir["#{package.root}/lib/*.js"].each do |file| + Dir["#{package.root}/lib/**/*.js"].each do |file| lib_js_errors = linter.lint(File.read(file)) if lib_js_errors.any? jshint_errors += [ JSHintValidationError.new(file, lib_js_errors) ] From 31c578212aa6b3dea5956d142169c2edd066e160 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 21 Mar 2014 10:58:09 +1100 Subject: [PATCH 05/27] Add subdirectories to spec path --- spec/validations/source_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/validations/source_spec.rb b/spec/validations/source_spec.rb index cffb7592..5c76d450 100644 --- a/spec/validations/source_spec.rb +++ b/spec/validations/source_spec.rb @@ -41,7 +41,7 @@ it 'should have a jslint error when missing semicolon in lib js file' do source = mock('AppFile', :relative_path => 'app.js', :read => "") package = mock('Package', :root => '.', :files => [source], :requirements_only => false) - Dir.stub('[]').with('./lib/*.js').and_return(['a.js']) + Dir.stub('[]').with('./lib/**/*.js').and_return(['a.js']) File.stub(:read).with('a.js').and_return("var a = 1") errors = ZendeskAppsSupport::Validations::Source.call(package) From b5913df94c739712ba289bd0d559d10fd7ca1563 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 21 Mar 2014 14:50:49 +1100 Subject: [PATCH 06/27] Only display relative path of invalid libraries --- lib/zendesk_apps_support/validations/source.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index f239e11d..8ba27512 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -35,7 +35,7 @@ def call(package) Dir["#{package.root}/lib/**/*.js"].each do |file| lib_js_errors = linter.lint(File.read(file)) if lib_js_errors.any? - jshint_errors += [ JSHintValidationError.new(file, lib_js_errors) ] + jshint_errors += [ JSHintValidationError.new(Pathname.new(file).relative_path_from(package.root), lib_js_errors) ] end end jshint_errors From 58e0c503aa569cadff940fc92cced155e89e4207 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 21 Mar 2014 15:15:01 +1100 Subject: [PATCH 07/27] Updated spec --- spec/invalid_app/app.css | 6 ++++++ spec/invalid_app/app.js | 13 +++++++++++++ spec/invalid_app/assets/logo-small.png | Bin 0 -> 19224 bytes spec/invalid_app/assets/logo.png | Bin 0 -> 19224 bytes spec/invalid_app/lib/invalid.js | 2 ++ spec/invalid_app/manifest.json | 11 +++++++++++ spec/invalid_app/templates/layout.hdbs | 11 +++++++++++ spec/invalid_app/translations/en.json | 5 +++++ spec/validations/source_spec.rb | 9 +++------ 9 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 spec/invalid_app/app.css create mode 100644 spec/invalid_app/app.js create mode 100644 spec/invalid_app/assets/logo-small.png create mode 100644 spec/invalid_app/assets/logo.png create mode 100644 spec/invalid_app/lib/invalid.js create mode 100644 spec/invalid_app/manifest.json create mode 100644 spec/invalid_app/templates/layout.hdbs create mode 100644 spec/invalid_app/translations/en.json diff --git a/spec/invalid_app/app.css b/spec/invalid_app/app.css new file mode 100644 index 00000000..160ae16b --- /dev/null +++ b/spec/invalid_app/app.css @@ -0,0 +1,6 @@ +h1 { + color: red; + span { + color: green; + } +} diff --git a/spec/invalid_app/app.js b/spec/invalid_app/app.js new file mode 100644 index 00000000..87720ba4 --- /dev/null +++ b/spec/invalid_app/app.js @@ -0,0 +1,13 @@ +(function() { + + return { + events: { + 'app.activated':'doSomething' + }, + + doSomething: function() { + console.log("Invalid app"); + } + }; + +}()); diff --git a/spec/invalid_app/assets/logo-small.png b/spec/invalid_app/assets/logo-small.png new file mode 100644 index 0000000000000000000000000000000000000000..7f1cfd38b8cc50064e776eabffc138d762b2ea0d GIT binary patch literal 19224 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

&RUZAWN#6?P|T69z)n3(pFoZ>L(%n@A9u5W5E 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 literal 0 HcmV?d00001 diff --git a/spec/invalid_app/assets/logo.png b/spec/invalid_app/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7f1cfd38b8cc50064e776eabffc138d762b2ea0d GIT binary patch literal 19224 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

&RUZAWN#6?P|T69z)n3(pFoZ>L(%n@A9u5W5E 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 literal 0 HcmV?d00001 diff --git a/spec/invalid_app/lib/invalid.js b/spec/invalid_app/lib/invalid.js new file mode 100644 index 00000000..850247d1 --- /dev/null +++ b/spec/invalid_app/lib/invalid.js @@ -0,0 +1,2 @@ +var b = {} +module.exports = b; diff --git a/spec/invalid_app/manifest.json b/spec/invalid_app/manifest.json new file mode 100644 index 00000000..f8b98524 --- /dev/null +++ b/spec/invalid_app/manifest.json @@ -0,0 +1,11 @@ +{ + "name": "ABC", + "author": { + "name": "John Smith", + "email": "john@example.com" + }, + "defaultLocale": "en", + "private": true, + "location": "ticket_sidebar", + "frameworkVersion": "0.5" +} diff --git a/spec/invalid_app/templates/layout.hdbs b/spec/invalid_app/templates/layout.hdbs new file mode 100644 index 00000000..4666c263 --- /dev/null +++ b/spec/invalid_app/templates/layout.hdbs @@ -0,0 +1,11 @@ +

+
+
+ + diff --git a/spec/invalid_app/translations/en.json b/spec/invalid_app/translations/en.json new file mode 100644 index 00000000..2a8d1fcf --- /dev/null +++ b/spec/invalid_app/translations/en.json @@ -0,0 +1,5 @@ +{ + "app": { + "name": "Buddha Machine" + } +} diff --git a/spec/validations/source_spec.rb b/spec/validations/source_spec.rb index 5c76d450..fcf63383 100644 --- a/spec/validations/source_spec.rb +++ b/spec/validations/source_spec.rb @@ -39,12 +39,9 @@ end it 'should have a jslint error when missing semicolon in lib js file' do - source = mock('AppFile', :relative_path => 'app.js', :read => "") - package = mock('Package', :root => '.', :files => [source], :requirements_only => false) - Dir.stub('[]').with('./lib/**/*.js').and_return(['a.js']) - File.stub(:read).with('a.js').and_return("var a = 1") - errors = ZendeskAppsSupport::Validations::Source.call(package) + package = ZendeskAppsSupport::Package.new('spec/invalid_app') + errors = ZendeskAppsSupport::Validations::Source.call(package) - errors.first.to_s.should eql "JSHint error in a.js: \n L1: Missing semicolon." + errors.first.to_s.should eql "JSHint error in lib/invalid.js: \n L1: Missing semicolon." end end From a99390b94d3df69bff6dbf9c33878c4e3ee9b6bf Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Wed, 26 Mar 2014 15:17:38 +1100 Subject: [PATCH 08/27] Use sprockets commonjs module, allowing relative paths --- .../assets/require.js.erb | 40 +++++++++++++++---- spec/package_spec.rb | 40 +++++++++++++++---- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/lib/zendesk_apps_support/assets/require.js.erb b/lib/zendesk_apps_support/assets/require.js.erb index 0900a004..72113e33 100644 --- a/lib/zendesk_apps_support/assets/require.js.erb +++ b/lib/zendesk_apps_support/assets/require.js.erb @@ -1,20 +1,46 @@ var require = (function() { var modules = {}, cache = {}; - var require = function(path) { - var module = cache[path]; + var require = function(name, root) { + var path = expand(root, name), indexPath = expand(path, './index'), module, fn; + module = cache[path] || cache[indexPath]; if (module) { return module; - } else if (modules[path]) { - module = {exports: {}}; - modules[path].call(null, require, module); + } else if (fn = modules[path] || modules[path = indexPath]) { + module = {id: path, exports: {}}; cache[path] = module.exports; - return cache[path]; + fn(module.exports, function(name) { + return require(name, dirname(path)); + }, module); + return cache[path] = module.exports; } else { - throw 'module ' + path + ' not found'; + throw 'module ' + name + ' not found'; } }; + var expand = function(root, name) { + var results = [], parts, part; + // If path is relative + if (/^\.\.?(\/|$)/.test(name)) { + parts = [root, name].join('/').split('/'); + } else { + parts = name.split('/'); + } + for (var i = 0, length = parts.length; i < length; i++) { + part = parts[i]; + if (part == '..') { + results.pop(); + } else if (part != '.' && part != '') { + results.push(part); + } + } + return results.join('/'); + }; + + var dirname = function(path) { + return path.split('/').slice(0, -1).join('/'); + }; + modules = { <% modules.each do |path, content| %> '<%= path %>': function(require, module) { diff --git a/spec/package_spec.rb b/spec/package_spec.rb index d93da45f..d8bd5d98 100644 --- a/spec/package_spec.rb +++ b/spec/package_spec.rb @@ -54,20 +54,46 @@ var require = (function() { var modules = {}, cache = {}; - var require = function(path) { - var module = cache[path]; + var require = function(name, root) { + var path = expand(root, name), indexPath = expand(path, './index'), module, fn; + module = cache[path] || cache[indexPath]; if (module) { return module; - } else if (modules[path]) { - module = {exports: {}}; - modules[path].call(null, require, module); + } else if (fn = modules[path] || modules[path = indexPath]) { + module = {id: path, exports: {}}; cache[path] = module.exports; - return cache[path]; + fn(module.exports, function(name) { + return require(name, dirname(path)); + }, module); + return cache[path] = module.exports; } else { - throw 'module ' + path + ' not found'; + throw 'module ' + name + ' not found'; } }; + var expand = function(root, name) { + var results = [], parts, part; + // If path is relative + if (/^\\.\\.?(\\/|$)/.test(name)) { + parts = [root, name].join('/').split('/'); + } else { + parts = name.split('/'); + } + for (var i = 0, length = parts.length; i < length; i++) { + part = parts[i]; + if (part == '..') { + results.pop(); + } else if (part != '.' && part != '') { + results.push(part); + } + } + return results.join('/'); + }; + + var dirname = function(path) { + return path.split('/').slice(0, -1).join('/'); + }; + modules = { 'lib/a.js': function(require, module) { var a = { From 1b13c8d70523c019cbd58a0cfa0814887962f3e2 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Wed, 7 May 2014 17:10:18 +1000 Subject: [PATCH 09/27] Remove require module --- .../assets/require.js.erb | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 lib/zendesk_apps_support/assets/require.js.erb diff --git a/lib/zendesk_apps_support/assets/require.js.erb b/lib/zendesk_apps_support/assets/require.js.erb deleted file mode 100644 index 72113e33..00000000 --- a/lib/zendesk_apps_support/assets/require.js.erb +++ /dev/null @@ -1,54 +0,0 @@ -var require = (function() { - var modules = {}, cache = {}; - - var require = function(name, root) { - var path = expand(root, name), indexPath = expand(path, './index'), module, fn; - module = cache[path] || cache[indexPath]; - if (module) { - return module; - } else if (fn = modules[path] || modules[path = indexPath]) { - module = {id: path, exports: {}}; - cache[path] = module.exports; - fn(module.exports, function(name) { - return require(name, dirname(path)); - }, module); - return cache[path] = module.exports; - } else { - throw 'module ' + name + ' not found'; - } - }; - - var expand = function(root, name) { - var results = [], parts, part; - // If path is relative - if (/^\.\.?(\/|$)/.test(name)) { - parts = [root, name].join('/').split('/'); - } else { - parts = name.split('/'); - } - for (var i = 0, length = parts.length; i < length; i++) { - part = parts[i]; - if (part == '..') { - results.pop(); - } else if (part != '.' && part != '') { - results.push(part); - } - } - return results.join('/'); - }; - - var dirname = function(path) { - return path.split('/').slice(0, -1).join('/'); - }; - - modules = { - <% modules.each do |path, content| %> - '<%= path %>': function(require, module) { -<%= content %> - }, - <% end %> - eom: undefined - }; - - return require; -})(); From f6d6805d0d9a7d29a6bf49dc210dd1e92dcef671 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Wed, 7 May 2014 17:12:00 +1000 Subject: [PATCH 10/27] Load modules, ZAF app scope instance, indentation --- lib/zendesk_apps_support/assets/src.js.erb | 45 +++++++++++++--------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/lib/zendesk_apps_support/assets/src.js.erb b/lib/zendesk_apps_support/assets/src.js.erb index 0397bfd6..7ddf502a 100644 --- a/lib/zendesk_apps_support/assets/src.js.erb +++ b/lib/zendesk_apps_support/assets/src.js.erb @@ -1,26 +1,33 @@ (function() { - with( require('apps/framework/app_scope') ) { + with( ZendeskApps.AppScope.create() ) { + require.modules = { + <% modules.each do |path, content| %> + '<%= path %>': function(exports, require, module) { + <%= content %> + }, + <% end %> + eom: undefined + }; - var source = <%= source %>; + var source = <%= source %>; + var app = ZendeskApps.defineApp(source) + .reopenClass({ location: <%= location.to_json %> }) + .reopen({ + assetUrlPrefix: <%= asset_url_prefix.to_json %>, + appClassName: <%= app_class_name.to_json %>, + author: { + name: <%= author[:name].to_json %>, + email: <%= author[:email].to_json %> + }, + translations: <%= translations.to_json %>, + templates: <%= templates.to_json %>, + frameworkVersion: <%= framework_version.to_json %>, + }); - ZendeskApps[<%= name.to_json %>] = ZendeskApps.defineApp(source) - .reopenClass({ location: <%= location.to_json %> }) - .reopen({ - assetUrlPrefix: <%= asset_url_prefix.to_json %>, - appClassName: <%= app_class_name.to_json %>, - author: { - name: <%= author[:name].to_json %>, - email: <%= author[:email].to_json %> - }, - translations: <%= translations.to_json %>, - templates: <%= templates.to_json %>, - frameworkVersion: <%= framework_version.to_json %> - }); - - } - - ZendeskApps[<%= name.to_json %>].install({"id": <%= app_id %>, "app_id": <%= app_id %>, "settings": <%= settings.to_json %>}); + ZendeskApps[<%= name.to_json %>] = app; + } + ZendeskApps[<%= name.to_json %>].install({"id": <%= app_id %>, "app_id": <%= app_id %>, "settings": <%= settings.to_json %>}); }()); ZendeskApps.trigger && ZendeskApps.trigger('ready'); From 2c699c7cea70138be736215c758e002c65e42f9d Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Wed, 7 May 2014 17:12:36 +1000 Subject: [PATCH 11/27] Pass modules to src, make relative from /lib --- lib/zendesk_apps_support/package.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index 9e46a1e1..69521275 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -9,13 +9,13 @@ class Package DEFAULT_LAYOUT = Erubis::Eruby.new( File.read(File.expand_path('../assets/default_template.html.erb', __FILE__)) ) DEFAULT_SCSS = File.read(File.expand_path('../assets/default_styles.scss', __FILE__)) SRC_TEMPLATE = Erubis::Eruby.new( File.read(File.expand_path('../assets/src.js.erb', __FILE__)) ) - MODULES_TEMPLATE = Erubis::Eruby.new( File.read(File.expand_path('../assets/require.js.erb', __FILE__)) ) - attr_reader :root, :warnings + attr_reader :lib_root, :root, :warnings attr_accessor :requirements_only def initialize(dir) @root = Pathname.new(File.expand_path(dir)) + @lib_root = Pathname.new(File.join(@root, 'lib')) @warnings = [] @requirements_only = false end @@ -48,19 +48,19 @@ def validate end def compiled_js - return read_file("app.js") unless has_lib_js? + return read_file("app.js") + end + + def modules_js + return unless has_lib_js? modules = {} lib_files.each do |file| - name = Pathname.new(file).relative_path_from(@root) + name = Pathname.new(file).relative_path_from(@lib_root) content = File.read(file) modules[name] = content end - - insert = MODULES_TEMPLATE.result(:modules => modules) - original = read_file("app.js") - - original.sub(/^\s*\(\s*function\s*\(\s*\)\s*\{/, "(function() {\n#{insert}") + modules end def files @@ -68,7 +68,7 @@ def files end def lib_files - @lib_files ||= Dir["#{@root}/lib/**/*.js"] + @lib_files ||= Dir["#{@lib_root}/**/*.js"] end def template_files @@ -118,7 +118,8 @@ def readified_js(app_name, app_id, asset_url_prefix, settings={}) :framework_version => framework_version, :templates => templates, :settings => settings, - :app_id => app_id + :app_id => app_id, + :modules => modules_js ) end From c1b77f229f78c5586c312f244972091b1ad9b49b Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Thu, 8 May 2014 13:07:31 +1000 Subject: [PATCH 12/27] Implicit return --- lib/zendesk_apps_support/package.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index 69521275..fc5e3359 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -48,7 +48,7 @@ def validate end def compiled_js - return read_file("app.js") + read_file("app.js") end def modules_js From b402bc97d16f7c8b2cb551a8081bed1ae390bc4b Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Thu, 8 May 2014 13:19:38 +1000 Subject: [PATCH 13/27] Refactor modules collection --- lib/zendesk_apps_support/package.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index fc5e3359..adf1bbf0 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -53,14 +53,12 @@ def compiled_js def modules_js return unless has_lib_js? - - modules = {} - lib_files.each do |file| +return nil + lib_files.each_with_object({}) do |file, modules| name = Pathname.new(file).relative_path_from(@lib_root) content = File.read(file) modules[name] = content end - modules end def files From 15514f08a181eff8f362f79ea720918b8713e88a Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Thu, 8 May 2014 13:20:06 +1000 Subject: [PATCH 14/27] Add modules guard --- lib/zendesk_apps_support/assets/src.js.erb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/zendesk_apps_support/assets/src.js.erb b/lib/zendesk_apps_support/assets/src.js.erb index 7ddf502a..324e05e1 100644 --- a/lib/zendesk_apps_support/assets/src.js.erb +++ b/lib/zendesk_apps_support/assets/src.js.erb @@ -1,5 +1,6 @@ (function() { with( ZendeskApps.AppScope.create() ) { + <% unless modules.nil? %> require.modules = { <% modules.each do |path, content| %> '<%= path %>': function(exports, require, module) { @@ -8,6 +9,7 @@ <% end %> eom: undefined }; + <% end %> var source = <%= source %>; var app = ZendeskApps.defineApp(source) From f162dc845dc7cb87c4ef99d80914de0d7e3dc94a Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Thu, 8 May 2014 13:20:42 +1000 Subject: [PATCH 15/27] Remove testing stuff, oops --- lib/zendesk_apps_support/package.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index adf1bbf0..4a0bd8da 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -53,7 +53,7 @@ def compiled_js def modules_js return unless has_lib_js? -return nil + lib_files.each_with_object({}) do |file, modules| name = Pathname.new(file).relative_path_from(@lib_root) content = File.read(file) From ed2266f650be6b58c111a34486584a0a1ecd555f Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Thu, 8 May 2014 15:03:52 +1000 Subject: [PATCH 16/27] Refactor --- .../validations/source.rb | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index 8df0cbf3..1fba89c2 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -21,30 +21,34 @@ module Source class < Date: Thu, 8 May 2014 15:08:13 +1000 Subject: [PATCH 17/27] Added line break for readibility --- lib/zendesk_apps_support/assets/src.js.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/zendesk_apps_support/assets/src.js.erb b/lib/zendesk_apps_support/assets/src.js.erb index 324e05e1..ad804b50 100644 --- a/lib/zendesk_apps_support/assets/src.js.erb +++ b/lib/zendesk_apps_support/assets/src.js.erb @@ -12,6 +12,7 @@ <% end %> var source = <%= source %>; + var app = ZendeskApps.defineApp(source) .reopenClass({ location: <%= location.to_json %> }) .reopen({ From aacb466ee06eb705c75e9eb6d9c6e7e5f1f49d4f Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Thu, 8 May 2014 15:21:18 +1000 Subject: [PATCH 18/27] Better method names, `each_with_object` --- lib/zendesk_apps_support/validations/source.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index 1fba89c2..49825f04 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -31,22 +31,20 @@ def call(package) return [ ValidationError.new(:missing_source) ] unless app - jshint_files(files) + jshint_errors(files) end private - def jshint_file(file) + def jshint_error(file) errors = linter.lint(file.read) [ JSHintValidationError.new(file.relative_path, errors) ] if errors.any? end - def jshint_files(files) - jshint_errors = [] - files.each do |file| - jshint_errors << jshint_file(file) + def jshint_errors(files) + files.each_with_object([]) do |file, errors| + errors << jshint_error(file) end - jshint_errors end def linter From 47ef299c5dd393845148589f63f41d2ffc3fde13 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 9 May 2014 13:17:41 +1000 Subject: [PATCH 19/27] Remove additional whitespace --- lib/zendesk_apps_support/assets/src.js.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zendesk_apps_support/assets/src.js.erb b/lib/zendesk_apps_support/assets/src.js.erb index ad804b50..29b9dff7 100644 --- a/lib/zendesk_apps_support/assets/src.js.erb +++ b/lib/zendesk_apps_support/assets/src.js.erb @@ -11,9 +11,9 @@ }; <% end %> - var source = <%= source %>; + var source = <%= source %>; - var app = ZendeskApps.defineApp(source) + var app = ZendeskApps.defineApp(source) .reopenClass({ location: <%= location.to_json %> }) .reopen({ assetUrlPrefix: <%= asset_url_prefix.to_json %>, From be0e782d787d4cd9003a23bbeb1260af32a7b524 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 9 May 2014 13:57:18 +1000 Subject: [PATCH 20/27] Rename method, load `lib_files` as `AppFile`s --- lib/zendesk_apps_support/package.rb | 10 +++++----- lib/zendesk_apps_support/validations/source.rb | 3 +-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index 4a0bd8da..c4fda827 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -51,12 +51,12 @@ def compiled_js read_file("app.js") end - def modules_js + def commonjs_modules return unless has_lib_js? lib_files.each_with_object({}) do |file, modules| - name = Pathname.new(file).relative_path_from(@lib_root) - content = File.read(file) + name = file.relative_path + content = file.read modules[name] = content end end @@ -66,7 +66,7 @@ def files end def lib_files - @lib_files ||= Dir["#{@lib_root}/**/*.js"] + @lib_files ||= files.select { |f| f =~ /^lib\/.*\.js$/ } end def template_files @@ -117,7 +117,7 @@ def readified_js(app_name, app_id, asset_url_prefix, settings={}) :templates => templates, :settings => settings, :app_id => app_id, - :modules => modules_js + :modules => commonjs_modules ) end diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index 49825f04..db58570e 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -22,8 +22,7 @@ module Source class < Date: Fri, 9 May 2014 15:47:42 +1000 Subject: [PATCH 21/27] Rename method --- lib/zendesk_apps_support/package.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index c4fda827..0492ea49 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -47,7 +47,7 @@ def validate end end - def compiled_js + def app_js read_file("app.js") end @@ -95,7 +95,7 @@ def app_translations def readified_js(app_name, app_id, asset_url_prefix, settings={}) manifest = manifest_json - source = compiled_js + source = app_js name = app_name || manifest[:name] || 'Local App' location = manifest[:location] app_class_name = "app-#{app_id}" From 213dc787a08834c4ad542576e1e62125246e1a40 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 9 May 2014 15:47:58 +1000 Subject: [PATCH 22/27] Flatten jshint error collection --- lib/zendesk_apps_support/validations/source.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index db58570e..c0248101 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -21,7 +21,7 @@ module Source class < Date: Fri, 9 May 2014 15:48:13 +1000 Subject: [PATCH 23/27] Update specs --- spec/app/lib/a.txt | 0 spec/app/lib/nested/b.js | 5 ++ spec/package_spec.rb | 124 +++++++++++++------------------- spec/validations/source_spec.rb | 8 +-- 4 files changed, 60 insertions(+), 77 deletions(-) create mode 100644 spec/app/lib/a.txt create mode 100644 spec/app/lib/nested/b.js diff --git a/spec/app/lib/a.txt b/spec/app/lib/a.txt new file mode 100644 index 00000000..e69de29b diff --git a/spec/app/lib/nested/b.js b/spec/app/lib/nested/b.js new file mode 100644 index 00000000..45c749c2 --- /dev/null +++ b/spec/app/lib/nested/b.js @@ -0,0 +1,5 @@ +var b = { + name: 'This is B' +}; + +module.exports = b; diff --git a/spec/package_spec.rb b/spec/package_spec.rb index 3a99f382..57f358ad 100644 --- a/spec/package_spec.rb +++ b/spec/package_spec.rb @@ -7,7 +7,7 @@ describe 'files' do it 'should return all the files within the app folder excluding files in tmp folder' do - @package.files.map(&:relative_path).should =~ %w(app.css app.js assets/logo-small.png assets/logo.png lib/a.js manifest.json templates/layout.hdbs translations/en.json) + @package.files.map(&:relative_path).should =~ %w(app.css app.js assets/logo-small.png assets/logo.png lib/a.js lib/a.txt lib/nested/b.js manifest.json templates/layout.hdbs translations/en.json) end it 'should error out when manifest is missing' do @@ -30,6 +30,21 @@ end end + describe 'lib_files' do + it 'should return all the javascript files in the lib folder within the app folder' do + @package.lib_files.map(&:relative_path).should == %w(lib/a.js lib/nested/b.js) + end + end + + describe 'commonjs_modules' do + it 'should return an object with name value pairs containing the path and code' do + @package.commonjs_modules.should == { + "lib/a.js"=>"var a = {\n name: 'This is A'\n};\n\nmodule.exports = a;\n", + "lib/nested/b.js"=>"var b = {\n name: 'This is B'\n};\n\nmodule.exports = b;\n" + } + end + end + describe 'manifest_json' do it 'should return manifest json' do manifest = @package.manifest_json @@ -46,69 +61,32 @@ describe 'readified_js' do it 'should generate js ready for installation' do js = @package.readified_js(nil, 0, 'http://localhost:4567/') + + # require 'byebug'; byebug; expected =<\\n.app-0 header .logo {\\n background-image: url(\\"http://localhost:4567/logo-small.png\\"); }\\n.app-0 h1 {\\n color: red; }\\n .app-0 h1 span {\\n color: green; }\\n\\n
\\n \\n

{{setting \\"name\\"}}

\\n
\\n
\\n\\n"}, - frameworkVersion: "0.5" - }); - - } - - ZendeskApps["ABC"].install({"id": 0, "app_id": 0, "settings": {\"title\":\"ABC\"}}); - + var app = ZendeskApps.defineApp(source) + .reopenClass({ location: "ticket_sidebar" }) + .reopen({ + assetUrlPrefix: "http://localhost:4567/", + appClassName: "app-0", + author: { + name: "John Smith", + email: "john@example.com" + }, + translations: {"app":{"name":"Buddha Machine"}}, + templates: {"layout":"\\n
\\n \\n

{{setting \\"name\\"}}

\\n
\\n
\\n\\n"}, + frameworkVersion: "0.5", + }); + + ZendeskApps["ABC"] = app; + } + + ZendeskApps["ABC"].install({"id": 0, "app_id": 0, "settings": {"title":"ABC"}}); }()); ZendeskApps.trigger && ZendeskApps.trigger('ready'); diff --git a/spec/validations/source_spec.rb b/spec/validations/source_spec.rb index fcf63383..24701087 100644 --- a/spec/validations/source_spec.rb +++ b/spec/validations/source_spec.rb @@ -5,7 +5,7 @@ context 'when requirements only' do it 'should have an error when app.js is present' do files = [mock('AppFile', :relative_path => 'app.js')] - package = mock('Package', :files => files, :requirements_only => true) + package = mock('Package', :files => files, :lib_files => [], :requirements_only => true) errors = ZendeskAppsSupport::Validations::Source.call(package) errors.first.key.should eql :no_app_js_required @@ -13,7 +13,7 @@ it 'should not have an error when app.js is not present' do files = [mock('AppFile', :relative_path => nil)] - package = mock('Package', :files => files, :requirements_only => true) + package = mock('Package', :files => files, :lib_files => [], :requirements_only => true) errors = ZendeskAppsSupport::Validations::Source.call(package) errors.should be_empty @@ -23,7 +23,7 @@ context 'when not requirements only' do it 'should have an error when app.js is missing' do files = [mock('AppFile', :relative_path => 'abc.js')] - package = mock('Package', :files => files, :requirements_only => false) + package = mock('Package', :files => files, :lib_files => [], :requirements_only => false) errors = ZendeskAppsSupport::Validations::Source.call(package) errors.first.to_s.should eql 'Could not find app.js' @@ -32,7 +32,7 @@ it 'should have a jslint error when missing semicolon' do source = mock('AppFile', :relative_path => 'app.js', :read => "var a = 1") - package = mock('Package', :root => '.', :files => [source], :requirements_only => false) + package = mock('Package', :root => '.', :files => [source], :lib_files => [], :requirements_only => false) errors = ZendeskAppsSupport::Validations::Source.call(package) errors.first.to_s.should eql "JSHint error in app.js: \n L1: Missing semicolon." From 6ba893e1f9a2891e8a16571d210a6d03c1806b73 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 9 May 2014 16:40:53 +1000 Subject: [PATCH 24/27] Remove lib from rendered path --- lib/zendesk_apps_support/package.rb | 2 +- spec/app/app.js | 2 +- spec/package_spec.rb | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/zendesk_apps_support/package.rb b/lib/zendesk_apps_support/package.rb index 0492ea49..3d72ed70 100644 --- a/lib/zendesk_apps_support/package.rb +++ b/lib/zendesk_apps_support/package.rb @@ -55,7 +55,7 @@ def commonjs_modules return unless has_lib_js? lib_files.each_with_object({}) do |file, modules| - name = file.relative_path + name = file.relative_path.gsub!(/^lib\//, '') content = file.read modules[name] = content end diff --git a/spec/app/app.js b/spec/app/app.js index 8c31b6cb..289a534b 100644 --- a/spec/app/app.js +++ b/spec/app/app.js @@ -1,7 +1,7 @@ (function() { return { - a: require('lib/a.js'), + a: require('a.js'), events: { 'app.activated':'doSomething' diff --git a/spec/package_spec.rb b/spec/package_spec.rb index 57f358ad..798c84ca 100644 --- a/spec/package_spec.rb +++ b/spec/package_spec.rb @@ -39,8 +39,8 @@ describe 'commonjs_modules' do it 'should return an object with name value pairs containing the path and code' do @package.commonjs_modules.should == { - "lib/a.js"=>"var a = {\n name: 'This is A'\n};\n\nmodule.exports = a;\n", - "lib/nested/b.js"=>"var b = {\n name: 'This is B'\n};\n\nmodule.exports = b;\n" + "a.js"=>"var a = {\n name: 'This is A'\n};\n\nmodule.exports = a;\n", + "nested/b.js"=>"var b = {\n name: 'This is B'\n};\n\nmodule.exports = b;\n" } end end @@ -67,7 +67,7 @@ (function() { with( ZendeskApps.AppScope.create() ) { require.modules = { - 'lib/a.js': function(exports, require, module) { + 'a.js': function(exports, require, module) { var a = { name: 'This is A' }; @@ -75,7 +75,7 @@ module.exports = a; }, - 'lib/nested/b.js': function(exports, require, module) { + 'nested/b.js': function(exports, require, module) { var b = { name: 'This is B' }; @@ -89,7 +89,7 @@ var source = (function() { return { - a: require('lib/a.js'), + a: require('a.js'), events: { 'app.activated':'doSomething' From dcc0887ade53308d15a70019923f1a4de0490420 Mon Sep 17 00:00:00 2001 From: Travers McInerney Date: Fri, 9 May 2014 16:43:34 +1000 Subject: [PATCH 25/27] Missed a debug --- spec/package_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/package_spec.rb b/spec/package_spec.rb index 798c84ca..3062a48d 100644 --- a/spec/package_spec.rb +++ b/spec/package_spec.rb @@ -62,7 +62,6 @@ it 'should generate js ready for installation' do js = @package.readified_js(nil, 0, 'http://localhost:4567/') - # require 'byebug'; byebug; expected =< Date: Fri, 9 May 2014 16:46:34 +1000 Subject: [PATCH 26/27] Add a bunch more allowed globals --- lib/zendesk_apps_support/validations/source.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index c0248101..d64e1284 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -16,7 +16,7 @@ module Source # predefined globals: :predef => %w(_ console services helpers alert window document self JSON Base64 clearInterval clearTimeout setInterval setTimeout - require module) + require module exports top frames parent) }.freeze class < Date: Fri, 9 May 2014 16:59:24 +1000 Subject: [PATCH 27/27] Add guard for nil --- lib/zendesk_apps_support/validations/source.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/zendesk_apps_support/validations/source.rb b/lib/zendesk_apps_support/validations/source.rb index d64e1284..b6e26968 100644 --- a/lib/zendesk_apps_support/validations/source.rb +++ b/lib/zendesk_apps_support/validations/source.rb @@ -42,7 +42,8 @@ def jshint_error(file) def jshint_errors(files) files.each_with_object([]) do |file, errors| - errors << jshint_error(file) + error = jshint_error(file) + errors << error unless error.nil? end end