zendesk_apps_tools 2.3.0 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f6aebc0331def0f61f777fcfee6c885ca518751
4
- data.tar.gz: 8b5eb53908cb059769583cd42da31fe8758d5c62
3
+ metadata.gz: c9671e253af31f401823ee8fbe0ca6b9aed32b1c
4
+ data.tar.gz: ba2aa8edc896d0d1f3d22e5da360ea2e569dd94d
5
5
  SHA512:
6
- metadata.gz: cc89513fb13b00cee92a7362f3909f5e21a4da8260cd5347224d1490a473d988485bddfa0276c5503a74e84d10ae5473a2515c2b53f9996782c460e171cb175a
7
- data.tar.gz: 9fe853d0f697005c47bdbe8bf4d74ec0988dbd4da5b90216a2230509ab9126d685a7d920a013c99055cf22c18db939d9ceb941cc215a037ea1200176e4370f7b
6
+ metadata.gz: 869b048a92f9721b689030c45ec5f5b355cb7640a8d16c738d28664c600a6c52e039008bf26e11db2247af3879e0b5729e41173e09642f903861fcb88d58ba36
7
+ data.tar.gz: 988d22c6814670f837304c52365a7a07f67fd08d536ad5d2f57ce306a94fd8abb67ff708d6041085ba5afbec57223d4b936e47835cbad088a12cf68d4fb8f4e6
@@ -16,7 +16,7 @@ module ZendeskAppsTools
16
16
  require 'faraday'
17
17
  prepare_api_auth
18
18
  Faraday.new full_url do |f|
19
- f.request encoding
19
+ f.request encoding if encoding
20
20
  f.adapter :net_http
21
21
  f.basic_auth @username, @password
22
22
  end
@@ -10,13 +10,17 @@ module ZendeskAppsTools
10
10
  include Thor::Actions
11
11
  include ZendeskAppsTools::CommandHelpers
12
12
 
13
- map %w(-v) => :version
13
+ map %w[-v] => :version
14
+ DEFAULT_SERVER_PORT = '4567'
14
15
 
15
16
  source_root File.expand_path(File.join(File.dirname(__FILE__), '../..'))
16
17
 
17
18
  desc 'translate SUBCOMMAND', 'Manage translation files', hide: true
18
19
  subcommand 'translate', Translate
19
20
 
21
+ desc 'theme SUBCOMMAND', 'Development tools for Theming Center (Beta)', hide: false
22
+ subcommand 'theme', Theme
23
+
20
24
  desc 'bump SUBCOMMAND', 'Bump version for app', hide: true
21
25
  subcommand 'bump', Bump
22
26
 
@@ -125,13 +129,12 @@ module ZendeskAppsTools
125
129
 
126
130
  DEFAULT_SERVER_PATH = './'
127
131
  DEFAULT_CONFIG_PATH = './settings.yml'
128
- DEFAULT_SERVER_PORT = '4567'
129
132
  DEFAULT_APP_ID = 0
130
133
 
131
134
  desc 'server', 'Run a http server to serve the local app'
132
135
  shared_options(except: [:clean])
133
136
  method_option :config, default: DEFAULT_CONFIG_PATH, required: false, aliases: '-c'
134
- method_option :port, default: DEFAULT_SERVER_PORT, required: false
137
+ method_option :port, default: DEFAULT_SERVER_PORT, required: false, desc: 'Port for the http server to use.'
135
138
  method_option :app_id, default: DEFAULT_APP_ID, required: false, type: :numeric
136
139
  method_option :bind, required: false
137
140
  def server
@@ -206,10 +209,6 @@ module ZendeskAppsTools
206
209
  manifest.location_options.collect{ |option| option.location.product_code }
207
210
  end
208
211
 
209
- def setup_path(path)
210
- @destination_stack << relative_to_original_destination_root(path) unless @destination_stack.last == path
211
- end
212
-
213
212
  def settings
214
213
  settings_helper.get_settings_from_file(options[:config], manifest.original_parameters) ||
215
214
  settings_helper.get_settings_from_user_input(manifest.original_parameters)
@@ -25,5 +25,9 @@ module ZendeskAppsTools
25
25
  Cache.new(options)
26
26
  end
27
27
  end
28
+
29
+ def setup_path(path)
30
+ @destination_stack << relative_to_original_destination_root(path) unless @destination_stack.last == path
31
+ end
28
32
  end
29
33
  end
@@ -5,6 +5,7 @@ require 'zendesk_apps_support/package'
5
5
 
6
6
  module ZendeskAppsTools
7
7
  class Server < Sinatra::Base
8
+ set :server, :thin
8
9
  set :protection, except: :frame_options
9
10
  ZENDESK_DOMAINS_REGEX = %r{^http(?:s)?://[a-z0-9-]+\.(?:zendesk|zopim|zd-(?:dev|master|staging))\.com$}
10
11
 
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'zendesk_apps_tools/theming/common'
4
+
5
+ module ZendeskAppsTools
6
+ class Theme < Thor
7
+ include Thor::Actions
8
+ include ZendeskAppsTools::CommandHelpers
9
+ include ZendeskAppsTools::Theming::Common
10
+
11
+ desc 'preview', 'Preview a theme in development'
12
+ shared_options(except: %i[clean unattended])
13
+ method_option :port, default: Command::DEFAULT_SERVER_PORT, required: false, desc: 'Port for the http server to use.'
14
+ method_option :bind, required: false
15
+ method_option :role,
16
+ type: :string,
17
+ enum: %w[manager agent end_user anonymous],
18
+ default: 'manager',
19
+ desc: 'The role for the preview URL',
20
+ aliases: '-r'
21
+ method_option :livereload, type: :boolean, default: true, desc: 'Enable or disable live-reloading the preview when a change is made.'
22
+ def preview
23
+ setup_path(options[:path])
24
+ ensure_manifest!
25
+ require 'faraday'
26
+ full_upload
27
+ callbacks_after_upload = []
28
+ start_listener(callbacks_after_upload)
29
+ start_server(callbacks_after_upload)
30
+ end
31
+
32
+ no_commands do
33
+ def full_upload
34
+ say_status 'Generating', 'Generating theme from local files'
35
+ payload = generate_payload.merge(role: options[:role])
36
+ say_status 'Generating', 'OK'
37
+ say_status 'Uploading', 'Uploading theme'
38
+ connection = get_connection(nil)
39
+ connection.use Faraday::Response::RaiseError
40
+ connection.put do |req|
41
+ req.url '/hc/api/internal/theming/local_preview'
42
+ req.body = JSON.dump(payload)
43
+ req.headers['Content-Type'] = 'application/json'
44
+ end
45
+ say_status 'Uploading', 'OK'
46
+ say_status 'Ready', "#{connection.url_prefix}hc/admin/local_preview/start"
47
+ say "You can exit preview mode in the UI or by visiting #{connection.url_prefix}hc/admin/local_preview/stop"
48
+ rescue Faraday::Error::ClientError => e
49
+ say_status 'Uploading', "Failed: #{e.message}", :red
50
+ begin
51
+ error_hash = JSON.parse(e.response[:body])
52
+ broken_templates = error_hash['template_errors']
53
+ if broken_templates
54
+ broken_templates.each do |template_name, errors|
55
+ errors.each do |error|
56
+ say_status 'Error', "#{template_name} L#{error['line']}:#{error['column']}: #{error['description']}", :red
57
+ end
58
+ end
59
+ else
60
+ say_status 'Error', error_hash
61
+ end
62
+ rescue JSON::ParserError
63
+ say_error_and_exit 'Server error 😭'
64
+ end
65
+ end
66
+
67
+ def start_listener(callbacks_after_upload)
68
+ # TODO: do we need to stop the listener at some point?
69
+ require 'listen'
70
+ path = Pathname.new(theme_package_path('.')).cleanpath
71
+ listener = ::Listen.to(path, ignore: /\.zat/) do |modified, added, removed|
72
+ need_upload = false
73
+ if modified.any? { |file| file[/templates|manifest/] }
74
+ need_upload = true
75
+ end
76
+ if added.any? || removed.any?
77
+ need_upload = true
78
+ end
79
+ if need_upload
80
+ full_upload
81
+ end
82
+ callbacks_after_upload.each do |callback|
83
+ callback.call
84
+ end
85
+ end
86
+ listener.start
87
+ end
88
+
89
+ def generate_payload
90
+ payload = {}
91
+ templates = Dir.glob(theme_package_path('templates', '*.hbs'))
92
+ templates_payload = {}
93
+ templates.each do |template|
94
+ templates_payload[File.basename(template, '.hbs')] = File.read(template)
95
+ end
96
+ payload['templates'] = templates_payload
97
+ payload['templates']['document_head'] = inject_external_tags(payload['templates']['document_head'])
98
+ payload['templates']['css'] = ''
99
+ payload['templates']['js'] = ''
100
+ payload['templates']['assets'] = assets
101
+ payload['templates']['variables'] = settings_hash
102
+ payload
103
+ end
104
+
105
+ def inject_external_tags(head_template)
106
+ live_reload_script_tag = <<-html
107
+ <script type="text/javascript">
108
+ RACK_LIVERELOAD_PORT = 4567;
109
+ </script>
110
+ <script src="http://localhost:4567/__rack/livereload.js?host=localhost"></script>
111
+ html
112
+
113
+ js_tag = <<-html
114
+ <script src="http://localhost:4567/guide/script.js"></script>
115
+ html
116
+
117
+ css_tag = <<-html
118
+ <link rel="stylesheet" type="text/css" href="http://localhost:4567/guide/style.css">
119
+ html
120
+
121
+ template = StringIO.new
122
+ template << css_tag
123
+ template << head_template
124
+ template << js_tag
125
+ template << live_reload_script_tag
126
+ template.string
127
+ end
128
+
129
+ alias_method :ensure_manifest!, :manifest
130
+
131
+ def start_server(callbacks_after_upload)
132
+ require 'zendesk_apps_tools/theming/server'
133
+ if options[:livereload]
134
+ require 'rack-livereload'
135
+ require 'faye/websocket'
136
+ Faye::WebSocket.load_adapter('thin')
137
+ end
138
+
139
+ ZendeskAppsTools::Theming::Server.tap do |server|
140
+ server.set :bind, options[:bind] if options[:bind]
141
+ server.set :port, options[:port]
142
+ server.set :root, app_dir
143
+ server.set :public_folder, app_dir
144
+ server.set :livereload, options[:livereload]
145
+ server.set :callbacks_after_load, callbacks_after_upload
146
+ server.set :callback_map, {}
147
+ server.use Rack::LiveReload, live_reload_port: 4567 if options[:livereload]
148
+ server.run!
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,64 @@
1
+ module ZendeskAppsTools
2
+ module Theming
3
+ module Common
4
+ def theme_package_path(*file)
5
+ File.expand_path(File.join(app_dir, *file))
6
+ end
7
+
8
+ def url_for(package_file)
9
+ relative_path = relative_path_for(package_file)
10
+ path_parts = recursive_pathname_split(relative_path)
11
+ path_parts.shift
12
+ "http://localhost:4567/guide/#{path_parts.join('/')}"
13
+ end
14
+
15
+ def relative_path_for(filename)
16
+ Pathname.new(filename).relative_path_from(Pathname.new(File.expand_path(app_dir))).cleanpath
17
+ end
18
+
19
+ def assets
20
+ assets = Dir.glob(theme_package_path('assets', '*'))
21
+ assets.each_with_object({}) do |asset, asset_payload|
22
+ asset_payload[File.basename(asset)] = url_for(asset)
23
+ end
24
+ end
25
+
26
+ def assets_hash
27
+ assets.each_with_object({}) do |(k,v), h|
28
+ parametrized = k.gsub(/[^a-z0-9\-_]+/, '-')
29
+ h["assets-#{parametrized}"] = v
30
+ end
31
+ end
32
+
33
+ def manifest
34
+ full_manifest_path = theme_package_path('manifest.json')
35
+ JSON.parse(File.read(full_manifest_path))
36
+ rescue Errno::ENOENT
37
+ say_error_and_exit "There's no manifest file in #{full_manifest_path}"
38
+ rescue JSON::ParserError
39
+ say_error_and_exit "The manifest file is invalid at #{full_manifest_path}"
40
+ end
41
+
42
+ def settings_hash
43
+ manifest['settings'].flat_map { |setting_group| setting_group['variables'] }.each_with_object({}) do |variable, result|
44
+ result[variable.fetch('identifier')] = value_for_setting(variable)
45
+ end
46
+ end
47
+
48
+ def value_for_setting(variable)
49
+ return variable.fetch('value') unless variable.fetch('type') == 'file'
50
+
51
+ files = Dir.glob(theme_package_path('settings', '*.*'))
52
+ file = files.find { |f| File.basename(f, '.*') == variable.fetch('identifier') }
53
+ url_for(file)
54
+ end
55
+
56
+ def recursive_pathname_split(relative_path)
57
+ split_path = relative_path.split
58
+ joined_directories = split_path[0]
59
+ return split_path if split_path[0] == joined_directories.split[0]
60
+ [*recursive_pathname_split(joined_directories), split_path[1]]
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sinatra/base'
4
+ require 'zendesk_apps_tools/theming/common'
5
+
6
+ module ZendeskAppsTools
7
+ module Theming
8
+ class Server < Sinatra::Base
9
+ include Common
10
+
11
+ get '/livereload' do
12
+ if settings.livereload && Faye::WebSocket.websocket?(env)
13
+ ws = Faye::WebSocket.new(env)
14
+
15
+ new_callback = ->() { ws.send(JSON.dump(command: 'reload', path: '')) }
16
+ settings.callback_map[ws] = new_callback
17
+ settings.callbacks_after_load.push(new_callback)
18
+
19
+ ws.onmessage = lambda do |event|
20
+ message = JSON.parse(event.data)
21
+ if message['command'] == 'hello'
22
+ ws.send(JSON.dump(
23
+ command: 'hello',
24
+ protocols: [
25
+ 'http://livereload.com/protocols/official-7',
26
+ 'http://livereload.com/protocols/official-8',
27
+ 'http://livereload.com/protocols/official-9',
28
+ 'http://livereload.com/protocols/2.x-origin-version-negotiation',
29
+ 'http://livereload.com/protocols/2.x-remote-control'
30
+ ],
31
+ serverName: 'ZAT LiveReload 2'
32
+ ))
33
+ end
34
+ end
35
+
36
+ ws.onclose = lambda do |event|
37
+ settings.callbacks_after_load.delete_if do |entry|
38
+ entry == settings.callback_map[ws]
39
+ end
40
+ settings.callback_map.delete ws
41
+ ws = nil
42
+ end
43
+
44
+ # Return async Rack response
45
+ ws.rack_response
46
+ else
47
+ [500, {}, 'Websocket Server Error']
48
+ end
49
+ end
50
+
51
+ get '/guide/style.css' do
52
+ content_type 'text/css'
53
+ style_css = theme_package_path('style.css')
54
+ raise Sinatra::NotFound unless File.exist?(style_css)
55
+ zass_source = File.read(style_css)
56
+ require 'zendesk_apps_tools/theming/zass_formatter'
57
+ response = ZassFormatter.format(zass_source, settings_hash.merge(assets_hash))
58
+ response
59
+ end
60
+
61
+ get '/guide/*' do
62
+ path = File.join(app_dir, *params[:splat])
63
+ return send_file path if File.exist?(path)
64
+ raise Sinatra::NotFound
65
+ end
66
+
67
+ def app_dir
68
+ settings.root
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sass'
4
+ require 'English'
5
+
6
+ module ZendeskAppsTools
7
+ module Theming
8
+ class ZassFormatter
9
+ COMMAND = /(?<command>lighten|darken)/i
10
+ COLOR = /(?<color>#\h{6}|#\h{3})/
11
+ PERCENTAGE = /(?<percentage>\d{1,3})%/
12
+ COLOR_COMMAND_REGEXP = /#{COMMAND}\s*\(\s*#{COLOR}\s*,\s*#{PERCENTAGE}\s*\)/
13
+
14
+ def self.format(raw, variables)
15
+ joined_keys = "(#{variables.keys.join('|')})"
16
+
17
+ keys_regex = /(\$#{joined_keys}\b)|(#\{\$#{joined_keys}\})/
18
+
19
+ substitution_hash = variables.each_with_object({}) do |(k, v), hash|
20
+ hash["$#{k}"] = v.to_s
21
+ hash["\#{$#{k}}"] = v.to_s
22
+ end
23
+
24
+ body = raw.to_s.dup
25
+ body.gsub!(keys_regex, substitution_hash)
26
+
27
+ # Color manipulation
28
+ body.gsub!(COLOR_COMMAND_REGEXP) do |whole_match|
29
+ if $LAST_MATCH_INFO[:command] == 'lighten'
30
+ color_adjust($LAST_MATCH_INFO[:color], $LAST_MATCH_INFO[:percentage].to_i, :+) || whole_match
31
+ else
32
+ color_adjust($LAST_MATCH_INFO[:color], $LAST_MATCH_INFO[:percentage].to_i, :-) || whole_match
33
+ end
34
+ end
35
+
36
+ body
37
+ end
38
+
39
+ def self.color_adjust(rgb, percentage, op)
40
+ color = Sass::Script::Value::Color.from_hex(rgb)
41
+
42
+ # Copied from:
43
+ # https://github.com/sass/sass/blob/3.4.21/lib/sass/script/functions.rb#L2671
44
+ new_color = color.with(lightness: color.lightness.public_send(op, percentage))
45
+
46
+ # We set the Sass Output to compressed
47
+ new_color.options = { style: :compressed }
48
+ new_color.to_sass
49
+ rescue ArgumentError
50
+ nil
51
+ end
52
+ end
53
+ end
54
+ end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module ZendeskAppsTools
3
- VERSION = '2.3.0'
3
+ VERSION = '2.4.0'
4
4
  end
@@ -1,5 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ZendeskAppsTools
2
4
  autoload :Command, 'zendesk_apps_tools/command'
3
5
  autoload :Translate, 'zendesk_apps_tools/translate'
6
+ autoload :Theme, 'zendesk_apps_tools/theme'
4
7
  autoload :LocaleIdentifier, 'zendesk_apps_tools/locale_identifier'
5
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: zendesk_apps_tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James A. Rosen
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2017-09-12 00:00:00.000000000 Z
14
+ date: 2017-09-26 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: thor
@@ -41,6 +41,20 @@ dependencies:
41
41
  - - "~>"
42
42
  - !ruby/object:Gem::Version
43
43
  version: 1.2.1
44
+ - !ruby/object:Gem::Dependency
45
+ name: thin
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - "~>"
49
+ - !ruby/object:Gem::Version
50
+ version: 1.7.2
51
+ type: :runtime
52
+ prerelease: false
53
+ version_requirements: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - "~>"
56
+ - !ruby/object:Gem::Version
57
+ version: 1.7.2
44
58
  - !ruby/object:Gem::Dependency
45
59
  name: sinatra
46
60
  requirement: !ruby/object:Gem::Requirement
@@ -111,6 +125,48 @@ dependencies:
111
125
  - - "~>"
112
126
  - !ruby/object:Gem::Version
113
127
  version: 0.3.1
128
+ - !ruby/object:Gem::Dependency
129
+ name: listen
130
+ requirement: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - "~>"
133
+ - !ruby/object:Gem::Version
134
+ version: '2.10'
135
+ type: :runtime
136
+ prerelease: false
137
+ version_requirements: !ruby/object:Gem::Requirement
138
+ requirements:
139
+ - - "~>"
140
+ - !ruby/object:Gem::Version
141
+ version: '2.10'
142
+ - !ruby/object:Gem::Dependency
143
+ name: rack-livereload
144
+ requirement: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ type: :runtime
150
+ prerelease: false
151
+ version_requirements: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ - !ruby/object:Gem::Dependency
157
+ name: faye-websocket
158
+ requirement: !ruby/object:Gem::Requirement
159
+ requirements:
160
+ - - "~>"
161
+ - !ruby/object:Gem::Version
162
+ version: 0.10.7
163
+ type: :runtime
164
+ prerelease: false
165
+ version_requirements: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - "~>"
168
+ - !ruby/object:Gem::Version
169
+ version: 0.10.7
114
170
  - !ruby/object:Gem::Dependency
115
171
  name: cucumber
116
172
  requirement: !ruby/object:Gem::Requirement
@@ -238,6 +294,10 @@ files:
238
294
  - lib/zendesk_apps_tools/package_helper.rb
239
295
  - lib/zendesk_apps_tools/server.rb
240
296
  - lib/zendesk_apps_tools/settings.rb
297
+ - lib/zendesk_apps_tools/theme.rb
298
+ - lib/zendesk_apps_tools/theming/common.rb
299
+ - lib/zendesk_apps_tools/theming/server.rb
300
+ - lib/zendesk_apps_tools/theming/zass_formatter.rb
241
301
  - lib/zendesk_apps_tools/translate.rb
242
302
  - lib/zendesk_apps_tools/version.rb
243
303
  - templates/translation.erb.tt
@@ -261,7 +321,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
261
321
  version: 1.3.6
262
322
  requirements: []
263
323
  rubyforge_project:
264
- rubygems_version: 2.6.11
324
+ rubygems_version: 2.6.13
265
325
  signing_key:
266
326
  specification_version: 4
267
327
  summary: Tools to help you develop Zendesk Apps.
@@ -276,3 +336,4 @@ test_files:
276
336
  - features/support/helpers.rb
277
337
  - features/support/webmock.rb
278
338
  - features/validate.feature
339
+ has_rdoc: