xat 1.32.0
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.
- checksums.yaml +7 -0
- data/LICENSE +176 -0
- data/README.md +38 -0
- data/app_template/README.md +14 -0
- data/app_template/app.css +0 -0
- data/app_template/app.js +12 -0
- data/app_template/assets/banner.png +0 -0
- data/app_template/assets/logo-promotion.png +0 -0
- data/app_template/assets/logo-small.png +0 -0
- data/app_template/assets/logo.png +0 -0
- data/app_template/manifest.json.tt +13 -0
- data/app_template/templates/layout.hdbs +10 -0
- data/app_template/translations/en.json +16 -0
- data/app_template_iframe/README.md +14 -0
- data/app_template_iframe/assets/banner.png +0 -0
- data/app_template_iframe/assets/iframe.html +13 -0
- data/app_template_iframe/assets/logo-promotion.png +0 -0
- data/app_template_iframe/assets/logo-small.png +0 -0
- data/app_template_iframe/assets/logo.png +0 -0
- data/app_template_iframe/manifest.json.tt +15 -0
- data/app_template_iframe/translations/en.json +16 -0
- data/bin/xat +5 -0
- data/features/clean.feature +9 -0
- data/features/fixtures/quote_character_translation.json +6 -0
- data/features/new.feature +117 -0
- data/features/package.feature +22 -0
- data/features/step_definitions/app_steps.rb +103 -0
- data/features/support/env.rb +5 -0
- data/features/validate.feature +15 -0
- data/lib/xat.rb +5 -0
- data/lib/zendesk_apps_tools/api_connection.rb +33 -0
- data/lib/zendesk_apps_tools/array_patch.rb +22 -0
- data/lib/zendesk_apps_tools/bump.rb +60 -0
- data/lib/zendesk_apps_tools/cache.rb +25 -0
- data/lib/zendesk_apps_tools/command.rb +183 -0
- data/lib/zendesk_apps_tools/command_helpers.rb +20 -0
- data/lib/zendesk_apps_tools/common.rb +40 -0
- data/lib/zendesk_apps_tools/deploy.rb +94 -0
- data/lib/zendesk_apps_tools/directory.rb +28 -0
- data/lib/zendesk_apps_tools/locale_identifier.rb +10 -0
- data/lib/zendesk_apps_tools/manifest_handler.rb +72 -0
- data/lib/zendesk_apps_tools/package_helper.rb +30 -0
- data/lib/zendesk_apps_tools/server.rb +65 -0
- data/lib/zendesk_apps_tools/settings.rb +82 -0
- data/lib/zendesk_apps_tools/translate.rb +168 -0
- data/templates/translation.erb.tt +13 -0
- metadata +238 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'zendesk_apps_tools/cache'
|
2
|
+
require 'zendesk_apps_tools/common'
|
3
|
+
require 'zendesk_apps_tools/api_connection'
|
4
|
+
require 'zendesk_apps_tools/deploy'
|
5
|
+
require 'zendesk_apps_tools/directory'
|
6
|
+
require 'zendesk_apps_tools/package_helper'
|
7
|
+
require 'zendesk_apps_tools/settings'
|
8
|
+
require 'zendesk_apps_tools/translate'
|
9
|
+
require 'zendesk_apps_tools/bump'
|
10
|
+
|
11
|
+
module ZendeskAppsTools
|
12
|
+
module CommandHelpers
|
13
|
+
include ZendeskAppsTools::Cache
|
14
|
+
include ZendeskAppsTools::Common
|
15
|
+
include ZendeskAppsTools::APIConnection
|
16
|
+
include ZendeskAppsTools::Deploy
|
17
|
+
include ZendeskAppsTools::Directory
|
18
|
+
include ZendeskAppsTools::PackageHelper
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module ZendeskAppsTools
|
4
|
+
module Common
|
5
|
+
def api_request(url, request = Faraday.new)
|
6
|
+
request.get(url)
|
7
|
+
end
|
8
|
+
|
9
|
+
def say_error_and_exit(msg)
|
10
|
+
say msg, :red
|
11
|
+
exit 1
|
12
|
+
end
|
13
|
+
|
14
|
+
def get_value_from_stdin(prompt, opts = {})
|
15
|
+
options = {
|
16
|
+
valid_regex: opts[:allow_empty] ? /^.*$/ : /\S+/,
|
17
|
+
error_msg: 'Invalid, try again:',
|
18
|
+
allow_empty: false
|
19
|
+
}.merge(opts)
|
20
|
+
|
21
|
+
while input = ask(prompt)
|
22
|
+
return '' if input.empty? && options[:allow_empty]
|
23
|
+
if input =~ options[:valid_regex]
|
24
|
+
break
|
25
|
+
else
|
26
|
+
say(options[:error_msg], :red)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
input
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_password_from_stdin(prompt)
|
34
|
+
print "#{prompt} "
|
35
|
+
password = STDIN.noecho(&:gets).chomp
|
36
|
+
puts
|
37
|
+
password
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module ZendeskAppsTools
|
2
|
+
module Deploy
|
3
|
+
def deploy_app(connection_method, url, body)
|
4
|
+
body[:upload_id] = upload(options[:path]).to_s
|
5
|
+
|
6
|
+
connection = get_connection
|
7
|
+
|
8
|
+
response = connection.send(connection_method) do |req|
|
9
|
+
req.url url
|
10
|
+
req.headers[:content_type] = 'application/json'
|
11
|
+
req.body = JSON.generate body
|
12
|
+
end
|
13
|
+
|
14
|
+
check_status response
|
15
|
+
|
16
|
+
rescue Faraday::Error::ClientError, JSON::ParserError => e
|
17
|
+
say_error_and_exit e.message
|
18
|
+
end
|
19
|
+
|
20
|
+
def upload(path)
|
21
|
+
connection = get_connection :multipart
|
22
|
+
zipfile_path = options[:zipfile]
|
23
|
+
|
24
|
+
if zipfile_path
|
25
|
+
package_path = zipfile_path
|
26
|
+
else
|
27
|
+
package
|
28
|
+
package_path = Dir[File.join path, '/tmp/*.zip'].sort.last
|
29
|
+
end
|
30
|
+
|
31
|
+
payload = { uploaded_data: Faraday::UploadIO.new(package_path, 'application/zip') }
|
32
|
+
|
33
|
+
response = connection.post('/api/v2/apps/uploads.json', payload)
|
34
|
+
JSON.parse(response.body)['id']
|
35
|
+
|
36
|
+
rescue Faraday::Error::ClientError, JSON::ParserError => e
|
37
|
+
say_error_and_exit e.message
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_app_id
|
41
|
+
say_status 'Update', 'app ID is missing, searching...'
|
42
|
+
name = get_value_from_stdin('Enter the name of the app:')
|
43
|
+
|
44
|
+
connection = get_connection
|
45
|
+
|
46
|
+
all_apps = connection.get('/api/v2/apps.json').body
|
47
|
+
|
48
|
+
app_id = JSON.parse(all_apps)['apps'].find { |app| app['name'] == name }['id']
|
49
|
+
|
50
|
+
save_cache 'app_id' => app_id
|
51
|
+
app_id
|
52
|
+
rescue Faraday::Error::ClientError => e
|
53
|
+
say_error_and_exit e.message
|
54
|
+
end
|
55
|
+
|
56
|
+
def check_status(response)
|
57
|
+
job = response.body
|
58
|
+
job_response = JSON.parse(job)
|
59
|
+
say_error_and_exit job_response['error'] if job_response['error']
|
60
|
+
|
61
|
+
job_id = job_response['job_id']
|
62
|
+
check_job job_id
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_job(job_id)
|
66
|
+
connection = get_connection
|
67
|
+
|
68
|
+
loop do
|
69
|
+
response = connection.get("/api/v2/apps/job_statuses/#{job_id}")
|
70
|
+
info = JSON.parse(response.body)
|
71
|
+
status = info['status']
|
72
|
+
message = info['message']
|
73
|
+
app_id = info['app_id']
|
74
|
+
|
75
|
+
if %w(completed failed).include? status
|
76
|
+
case status
|
77
|
+
when 'completed'
|
78
|
+
save_cache 'app_id' => app_id
|
79
|
+
say_status @command, 'OK'
|
80
|
+
when 'failed'
|
81
|
+
say_status @command, message, :red
|
82
|
+
exit 1
|
83
|
+
end
|
84
|
+
break
|
85
|
+
end
|
86
|
+
|
87
|
+
say_status 'Status', status
|
88
|
+
sleep 3
|
89
|
+
end
|
90
|
+
rescue Faraday::Error::ClientError => e
|
91
|
+
say_error_and_exit e.message
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ZendeskAppsTools
|
2
|
+
module Directory
|
3
|
+
def app_dir
|
4
|
+
@app_dir ||= Pathname.new(destination_root)
|
5
|
+
end
|
6
|
+
|
7
|
+
def tmp_dir
|
8
|
+
@tmp_dir ||= Pathname.new(File.join(app_dir, 'tmp')).tap do |dir|
|
9
|
+
FileUtils.mkdir_p(dir)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def prompt_new_app_dir
|
14
|
+
prompt = "Enter a directory name to save the new app (will create the dir if it does not exist, default to current dir):\n"
|
15
|
+
opts = { valid_regex: /^(\w|\/|\\)*$/, allow_empty: true }
|
16
|
+
while @app_dir = get_value_from_stdin(prompt, opts)
|
17
|
+
@app_dir = './' and break if @app_dir.empty?
|
18
|
+
if !File.exist?(@app_dir)
|
19
|
+
break
|
20
|
+
elsif !File.directory?(@app_dir)
|
21
|
+
puts 'Invalid dir, try again:'
|
22
|
+
else
|
23
|
+
break
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'zendesk_apps_tools/array_patch'
|
2
|
+
|
3
|
+
module ZendeskAppsTools
|
4
|
+
module ManifestHandler
|
5
|
+
VERSION_PARTS = %i(major minor patch)
|
6
|
+
|
7
|
+
attr_reader :semver
|
8
|
+
|
9
|
+
VERSION_PARTS.each do |m|
|
10
|
+
define_method m do
|
11
|
+
load_manifest
|
12
|
+
read_version
|
13
|
+
normalize_version
|
14
|
+
super()
|
15
|
+
update_version
|
16
|
+
write_manifest
|
17
|
+
post_actions
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def manifest_json_path
|
24
|
+
'manifest.json'
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_manifest
|
28
|
+
manifest_json = File.read(manifest_json_path)
|
29
|
+
@manifest = JSON.load(manifest_json)
|
30
|
+
rescue => e
|
31
|
+
say(e.message, :red) and exit 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def read_version
|
35
|
+
version = @manifest.fetch('version', '0.0.0')
|
36
|
+
sem_parts = sub_semver(version)
|
37
|
+
@semver = sem_parts.names.map(&:to_sym).zip(sem_parts.to_a.drop(1)).to_h
|
38
|
+
end
|
39
|
+
|
40
|
+
def normalize_version
|
41
|
+
VERSION_PARTS.each do |part|
|
42
|
+
semver[part] = (semver[part] || '0').to_i
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def update_version
|
47
|
+
@manifest['version'] = version
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_manifest
|
51
|
+
File.open(manifest_json_path, 'w') do |f|
|
52
|
+
f.write(JSON.pretty_generate(@manifest))
|
53
|
+
f.write("\n")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def sub_semver(v)
|
58
|
+
v.match(/\A(?<v>v)?(?<major>\d+)(?:\.(?<minor>\d+)(?:\.(?<patch>\d+))?)?\Z/)
|
59
|
+
end
|
60
|
+
|
61
|
+
def version(v: false)
|
62
|
+
[
|
63
|
+
v ? 'v' : semver[:v],
|
64
|
+
[
|
65
|
+
semver[:major],
|
66
|
+
semver[:minor],
|
67
|
+
semver[:patch]
|
68
|
+
].compact.map(&:to_s).join('.')
|
69
|
+
].compact.join
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ZendeskAppsTools
|
2
|
+
require 'zendesk_apps_support'
|
3
|
+
|
4
|
+
module PackageHelper
|
5
|
+
include ZendeskAppsSupport
|
6
|
+
|
7
|
+
def app_package
|
8
|
+
@app_package ||= Package.new(app_dir.to_s)
|
9
|
+
end
|
10
|
+
|
11
|
+
def zip(archive_path)
|
12
|
+
Zip::ZipFile.open(archive_path, 'w') do |zipfile|
|
13
|
+
app_package.files.each do |file|
|
14
|
+
relative_path = file.relative_path
|
15
|
+
path = relative_path
|
16
|
+
say_status 'package', "adding #{path}"
|
17
|
+
|
18
|
+
# resolve symlink to source path
|
19
|
+
if File.symlink? file.absolute_path
|
20
|
+
path = File.expand_path(File.readlink(file.absolute_path), File.dirname(file.absolute_path))
|
21
|
+
end
|
22
|
+
if file.to_s == 'app.scss'
|
23
|
+
relative_path = relative_path.sub 'app.scss', 'app.css'
|
24
|
+
end
|
25
|
+
zipfile.add(relative_path, app_dir.join(path).to_s)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'zendesk_apps_support/package'
|
3
|
+
|
4
|
+
module ZendeskAppsTools
|
5
|
+
class Server < Sinatra::Base
|
6
|
+
set :protection, :except => :frame_options
|
7
|
+
last_mtime = Time.new(0)
|
8
|
+
ZENDESK_DOMAINS_REGEX = /^http(?:s)?:\/\/[a-z0-9-]+\.(?:zendesk|zopim|zd-(?:dev|master|staging))\.com$/
|
9
|
+
|
10
|
+
get '/app.js' do
|
11
|
+
access_control_allow_origin
|
12
|
+
content_type 'text/javascript'
|
13
|
+
|
14
|
+
if File.exists? settings.config
|
15
|
+
curr_mtime = File.stat(settings.config).mtime
|
16
|
+
if curr_mtime > last_mtime
|
17
|
+
settings_helper = ZendeskAppsTools::Settings.new
|
18
|
+
settings.parameters = settings_helper.get_settings_from_file(settings.config, settings.manifest)
|
19
|
+
last_mtime = curr_mtime
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
package = ZendeskAppsSupport::Package.new(settings.root, false)
|
24
|
+
app_name = package.manifest_json['name'] || 'Local App'
|
25
|
+
installation = ZendeskAppsSupport::Installation.new(
|
26
|
+
id: settings.app_id,
|
27
|
+
app_id: settings.app_id,
|
28
|
+
app_name: app_name,
|
29
|
+
enabled: true,
|
30
|
+
requirements: package.requirements_json,
|
31
|
+
settings: settings.parameters.merge({title: app_name}),
|
32
|
+
updated_at: Time.now.iso8601,
|
33
|
+
created_at: Time.now.iso8601
|
34
|
+
)
|
35
|
+
|
36
|
+
app_js = package.compile_js(
|
37
|
+
app_id: settings.app_id,
|
38
|
+
app_name: package.manifest_json['name'] || 'Local App',
|
39
|
+
assets_dir: "http://localhost:#{settings.port}/",
|
40
|
+
locale: params['locale']
|
41
|
+
)
|
42
|
+
|
43
|
+
ZendeskAppsSupport::Installed.new([app_js], [installation]).compile_js
|
44
|
+
end
|
45
|
+
|
46
|
+
get "/:file" do |file|
|
47
|
+
access_control_allow_origin
|
48
|
+
send_file File.join(settings.root, 'assets', file)
|
49
|
+
end
|
50
|
+
|
51
|
+
# This is for any preflight request
|
52
|
+
# It reads 'Access-Control-Request-Headers' to set 'Access-Control-Allow-Headers'
|
53
|
+
# And also sets 'Access-Control-Allow-Origin' header
|
54
|
+
options "*" do
|
55
|
+
access_control_allow_origin
|
56
|
+
headers 'Access-Control-Allow-Headers' => request.env['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] if request.env['HTTP_ORIGIN'] =~ ZENDESK_DOMAINS_REGEX
|
57
|
+
end
|
58
|
+
|
59
|
+
# This sets the 'Access-Control-Allow-Origin' header for requests coming from zendesk
|
60
|
+
def access_control_allow_origin
|
61
|
+
origin = request.env['HTTP_ORIGIN']
|
62
|
+
headers 'Access-Control-Allow-Origin' => origin if origin =~ ZENDESK_DOMAINS_REGEX
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'zendesk_apps_tools/common'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module ZendeskAppsTools
|
5
|
+
class Settings
|
6
|
+
def get_settings_from_user_input(user_input, parameters)
|
7
|
+
return {} if parameters.nil?
|
8
|
+
|
9
|
+
parameters.inject({}) do |settings, param|
|
10
|
+
if param['default']
|
11
|
+
input = user_input.get_value_from_stdin("Enter a value for parameter '#{param['name']}' or press 'Return' to use the default value '#{param['default']}':\n", allow_empty: true)
|
12
|
+
input = param['default'] if input.empty?
|
13
|
+
elsif param['required']
|
14
|
+
input = user_input.get_value_from_stdin("Enter a value for required parameter '#{param['name']}':\n")
|
15
|
+
else
|
16
|
+
input = user_input.get_value_from_stdin("Enter a value for optional parameter '#{param['name']}' or press 'Return' to skip:\n", allow_empty: true)
|
17
|
+
end
|
18
|
+
|
19
|
+
if param['type'] == 'checkbox'
|
20
|
+
input = convert_to_boolean_for_checkbox(input)
|
21
|
+
end
|
22
|
+
|
23
|
+
settings[param['name']] = input if input != ''
|
24
|
+
settings
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_settings_from_file(filepath, parameters)
|
29
|
+
return {} if parameters.nil?
|
30
|
+
return nil unless File.exist? filepath
|
31
|
+
|
32
|
+
begin
|
33
|
+
settings_file = File.read(filepath)
|
34
|
+
|
35
|
+
if filepath =~ /\.json$/ || settings_file =~ /\A\s*{/
|
36
|
+
settings_data = JSON.load(settings_file)
|
37
|
+
else
|
38
|
+
settings_data = YAML.load(settings_file)
|
39
|
+
end
|
40
|
+
|
41
|
+
settings_data.each do |index, setting|
|
42
|
+
if setting.is_a?(Hash) || setting.is_a?(Array)
|
43
|
+
settings_data[index] = JSON.dump(setting)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
rescue => err
|
47
|
+
puts "Failed to load #{filepath}"
|
48
|
+
puts err.message
|
49
|
+
return nil
|
50
|
+
end
|
51
|
+
|
52
|
+
parameters.inject({}) do |settings, param|
|
53
|
+
input = settings_data[param['name']]
|
54
|
+
|
55
|
+
if !input && param['default']
|
56
|
+
input = param['default']
|
57
|
+
end
|
58
|
+
|
59
|
+
if !input && param['required']
|
60
|
+
puts "'#{param['name']}' is required but not specified in the config file.\n"
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
|
64
|
+
if param['type'] == 'checkbox'
|
65
|
+
input = convert_to_boolean_for_checkbox(input)
|
66
|
+
end
|
67
|
+
|
68
|
+
settings[param['name']] = input if input != ''
|
69
|
+
settings
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def convert_to_boolean_for_checkbox(input)
|
76
|
+
unless [TrueClass, FalseClass].include?(input.class)
|
77
|
+
return (input =~ /^(true|t|yes|y|1)$/i) ? true : false
|
78
|
+
end
|
79
|
+
input
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|