xat 1.32.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +176 -0
  3. data/README.md +38 -0
  4. data/app_template/README.md +14 -0
  5. data/app_template/app.css +0 -0
  6. data/app_template/app.js +12 -0
  7. data/app_template/assets/banner.png +0 -0
  8. data/app_template/assets/logo-promotion.png +0 -0
  9. data/app_template/assets/logo-small.png +0 -0
  10. data/app_template/assets/logo.png +0 -0
  11. data/app_template/manifest.json.tt +13 -0
  12. data/app_template/templates/layout.hdbs +10 -0
  13. data/app_template/translations/en.json +16 -0
  14. data/app_template_iframe/README.md +14 -0
  15. data/app_template_iframe/assets/banner.png +0 -0
  16. data/app_template_iframe/assets/iframe.html +13 -0
  17. data/app_template_iframe/assets/logo-promotion.png +0 -0
  18. data/app_template_iframe/assets/logo-small.png +0 -0
  19. data/app_template_iframe/assets/logo.png +0 -0
  20. data/app_template_iframe/manifest.json.tt +15 -0
  21. data/app_template_iframe/translations/en.json +16 -0
  22. data/bin/xat +5 -0
  23. data/features/clean.feature +9 -0
  24. data/features/fixtures/quote_character_translation.json +6 -0
  25. data/features/new.feature +117 -0
  26. data/features/package.feature +22 -0
  27. data/features/step_definitions/app_steps.rb +103 -0
  28. data/features/support/env.rb +5 -0
  29. data/features/validate.feature +15 -0
  30. data/lib/xat.rb +5 -0
  31. data/lib/zendesk_apps_tools/api_connection.rb +33 -0
  32. data/lib/zendesk_apps_tools/array_patch.rb +22 -0
  33. data/lib/zendesk_apps_tools/bump.rb +60 -0
  34. data/lib/zendesk_apps_tools/cache.rb +25 -0
  35. data/lib/zendesk_apps_tools/command.rb +183 -0
  36. data/lib/zendesk_apps_tools/command_helpers.rb +20 -0
  37. data/lib/zendesk_apps_tools/common.rb +40 -0
  38. data/lib/zendesk_apps_tools/deploy.rb +94 -0
  39. data/lib/zendesk_apps_tools/directory.rb +28 -0
  40. data/lib/zendesk_apps_tools/locale_identifier.rb +10 -0
  41. data/lib/zendesk_apps_tools/manifest_handler.rb +72 -0
  42. data/lib/zendesk_apps_tools/package_helper.rb +30 -0
  43. data/lib/zendesk_apps_tools/server.rb +65 -0
  44. data/lib/zendesk_apps_tools/settings.rb +82 -0
  45. data/lib/zendesk_apps_tools/translate.rb +168 -0
  46. data/templates/translation.erb.tt +13 -0
  47. metadata +238 -0
@@ -0,0 +1,22 @@
1
+ Feature: package a zendesk app into a zip file
2
+
3
+ Background: create a new zendesk app
4
+ Given an app is created in directory "tmp/aruba"
5
+
6
+ Scenario: package a zendesk app by running 'xat package' command
7
+ When I run the command "xat package --path tmp/aruba" to package the app
8
+ And the command output should contain "adding app.js"
9
+ And the command output should contain "adding assets/logo-small.png"
10
+ And the command output should contain "adding assets/logo.png"
11
+ And the command output should contain "adding manifest.json"
12
+ And the command output should contain "adding templates/layout.hdbs"
13
+ And the command output should contain "adding translations/en.json"
14
+ And the command output should contain "created"
15
+ And the zip file should exist in directory "tmp/aruba/tmp"
16
+
17
+
18
+ Scenario: package a zendesk app by running 'xat package' command
19
+ When I create a symlink from "./templates/translation.erb.tt" to "tmp/aruba/assets/translation.erb.tt"
20
+ Then "tmp/aruba/assets/translation.erb.tt" should be a symlink
21
+ When I run the command "xat package --path tmp/aruba" to package the app
22
+ Then the zip file in "tmp/aruba/tmp" should not contain any symlinks
@@ -0,0 +1,103 @@
1
+ require 'fileutils'
2
+ require 'zip/zip'
3
+ require 'English'
4
+
5
+ When /^I move to the app directory$/ do
6
+ @previous_dir = Dir.pwd
7
+ Dir.chdir(@app_dir)
8
+ end
9
+
10
+ When /^I reset the working directory$/ do
11
+ Dir.chdir(@previous_dir)
12
+ end
13
+
14
+ Given /^an app directory "(.*?)" exists$/ do |app_dir|
15
+ @app_dir = app_dir
16
+ FileUtils.rm_rf(@app_dir)
17
+ FileUtils.mkdir_p(@app_dir)
18
+ end
19
+
20
+ Given /^an app is created in directory "(.*?)"$/ do |app_dir|
21
+ steps %(
22
+ Given an app directory "#{app_dir}" exists
23
+ And I run "xat new" command with the following details:
24
+ | author name | John Citizen |
25
+ | author email | john@example.com |
26
+ | author url | http://myapp.com |
27
+ | app name | John Test App |
28
+ | app dir | #{app_dir} |
29
+ )
30
+ end
31
+
32
+ When /^I run "(.*?)" command with the following details:$/ do |cmd, table|
33
+ IO.popen(cmd, 'w+') do |pipe|
34
+ # [ ['parameter name', 'value'] ]
35
+ table.raw.each do |row|
36
+ pipe.puts row.last
37
+ end
38
+ pipe.close_write
39
+ @output = pipe.readlines
40
+ @output.each { |line| puts line }
41
+ end
42
+ end
43
+
44
+ When /^I create a symlink from "(.*?)" to "(.*?)"$/ do |src, dest|
45
+ @link_destname = File.basename(dest)
46
+ # create a symlink
47
+ FileUtils.ln_s(src, dest)
48
+ end
49
+
50
+ When /^I run the command "(.*?)" to (validate|package|clean) the app$/ do |cmd, _action|
51
+ IO.popen(cmd, 'w+') do |pipe|
52
+ pipe.puts "\n"
53
+ pipe.close_write
54
+ @output = pipe.readlines
55
+ @output.each { |line| puts line }
56
+ end
57
+ end
58
+
59
+ Then /^the app file "(.*?)" is created with:$/ do |file, content|
60
+ File.read(file).chomp.gsub(' ', '').should == content.gsub(' ', '')
61
+ end
62
+
63
+ Then /^the app file "(.*?)" is created$/ do |filename|
64
+ File.exist?(filename).should be_truthy
65
+ end
66
+
67
+ Then /^the fixture "(.*?)" is used for "(.*?)"$/ do |fixture, app_file|
68
+ fixture_file = File.join('features', 'fixtures', fixture)
69
+ app_file_path = File.join(@app_dir, app_file)
70
+
71
+ FileUtils.cp(fixture_file, app_file_path)
72
+ end
73
+
74
+ Then /^the zip file should exist in directory "(.*?)"$/ do |path|
75
+ Dir[path + '/app-*.zip'].size.should == 1
76
+ end
77
+
78
+ Given /^I remove file "(.*?)"$/ do |file|
79
+ File.delete(file)
80
+ end
81
+
82
+ Then /^the zip file in "(.*?)" folder should not exist$/ do |path|
83
+ expect(Dir[path + '/app-*.zip'].size).to eq 0
84
+ end
85
+
86
+ Then /^it should pass the validation$/ do
87
+ @output.last.should =~ /OK/
88
+ $CHILD_STATUS.should == 0
89
+ end
90
+
91
+ Then /^the command output should contain "(.*?)"$/ do |output|
92
+ @output.join.should =~ /#{output}/
93
+ end
94
+
95
+ Then /^"(.*?)" should be a symlink$/ do |path|
96
+ File.symlink?(path).should be_truthy
97
+ end
98
+
99
+ Then /^the zip file in "(.*?)" should not contain any symlinks$/ do |path|
100
+ Zip::ZipFile.foreach Dir[path + '/app-*.zip'][0] do |p|
101
+ p.symlink?.should be_falsy
102
+ end
103
+ end
@@ -0,0 +1,5 @@
1
+ require 'aruba/cucumber'
2
+
3
+ Before do
4
+ @aruba_timeout_seconds = 15
5
+ end
@@ -0,0 +1,15 @@
1
+ Feature: validate a zendesk app
2
+
3
+ Validate a zendesk app by running 'xat package' command
4
+
5
+ Background: create a new zendesk app
6
+ Given an app is created in directory "tmp/aruba"
7
+
8
+ Scenario: valid app
9
+ When I run the command "xat validate --path tmp/aruba" to validate the app
10
+ Then it should pass the validation
11
+
12
+ Scenario: invalid app (missing manifest.json
13
+ Given I remove file "tmp/aruba/manifest.json"
14
+ When I run the command "xat validate --path tmp/aruba" to validate the app
15
+ Then the command output should contain "Could not find manifest.json"
data/lib/xat.rb ADDED
@@ -0,0 +1,5 @@
1
+ module ZendeskAppsTools
2
+ autoload :Command, 'zendesk_apps_tools/command'
3
+ autoload :Translate, 'zendesk_apps_tools/translate'
4
+ autoload :LocaleIdentifier, 'zendesk_apps_tools/locale_identifier'
5
+ end
@@ -0,0 +1,33 @@
1
+ module ZendeskAppsTools
2
+ module APIConnection
3
+ FULL_URL = /https?:\/\//
4
+ URL_TEMPLATE = 'https://%s.zendesk.com/'
5
+
6
+ def prepare_api_auth
7
+ @subdomain ||= fetch_cache('subdomain') || get_value_from_stdin('Enter your Zendesk subdomain or full Zendesk URL:')
8
+ @username ||= fetch_cache('username') || get_value_from_stdin('Enter your username:')
9
+ @password ||= fetch_cache('password') || get_password_from_stdin('Enter your password:')
10
+
11
+ save_cache 'subdomain' => @subdomain, 'username' => @username
12
+ end
13
+
14
+ def get_connection(encoding = :url_encoded)
15
+ prepare_api_auth
16
+ Faraday.new full_url do |f|
17
+ f.request encoding
18
+ f.adapter :net_http
19
+ f.basic_auth @username, @password
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def full_url
26
+ if FULL_URL =~ @subdomain
27
+ @subdomain
28
+ else
29
+ URL_TEMPLATE % @subdomain
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,22 @@
1
+ class Array
2
+ unless instance_methods.include? :to_h
3
+ def to_h
4
+ if elem_index = index { |elem| !elem.is_a?(Array) }
5
+ raise TypeError.new("wrong element type #{self[elem_index].class} at #{elem_index} (expected array)")
6
+ end
7
+
8
+ each_with_index.inject({}) do |hash, elem|
9
+ pair, index = elem
10
+
11
+ if pair.size != 2
12
+ raise ArgumentError.new("wrong array length at #{index} (expected 2, was #{pair.size})")
13
+ end
14
+
15
+ hash.tap do |h|
16
+ key, val = pair
17
+ h[key] = val
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,60 @@
1
+ require 'thor'
2
+ require 'json'
3
+
4
+ require 'zendesk_apps_tools/manifest_handler'
5
+
6
+ module ZendeskAppsTools
7
+ class Bump < Thor
8
+ include Thor::Actions
9
+ prepend ManifestHandler
10
+
11
+ SHARED_OPTIONS = {
12
+ ['commit', '-c'] => false,
13
+ ['message', '-m'] => nil,
14
+ ['tag', '-t'] => false
15
+ }
16
+
17
+ desc 'major', 'Bump major version'
18
+ method_options SHARED_OPTIONS
19
+ def major
20
+ semver[:major] += 1
21
+ semver[:minor] = 0
22
+ semver[:patch] = 0
23
+ end
24
+
25
+ desc 'minor', 'Bump minor version'
26
+ method_options SHARED_OPTIONS
27
+ def minor
28
+ semver[:minor] += 1
29
+ semver[:patch] = 0
30
+ end
31
+
32
+ desc 'patch', 'Bump patch version'
33
+ method_options SHARED_OPTIONS
34
+ def patch
35
+ semver[:patch] += 1
36
+ end
37
+
38
+ default_task :patch
39
+
40
+ private
41
+
42
+ def post_actions
43
+ return tag if options[:tag]
44
+ commit if options[:commit]
45
+ end
46
+
47
+ def commit
48
+ `git commit -am #{commit_message}`
49
+ end
50
+
51
+ def commit_message
52
+ options[:message] || version(v: true)
53
+ end
54
+
55
+ def tag
56
+ commit
57
+ `git tag #{version(v: true)}`
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,25 @@
1
+ module ZendeskAppsTools
2
+ module Cache
3
+ CACHE_FILE_NAME = '.xat'
4
+
5
+ def save_cache(hash)
6
+ return if options[:zipfile]
7
+
8
+ @cache = File.exist?(cache_path) ? JSON.parse(File.read(@cache_path)).update(hash) : hash
9
+ File.open(@cache_path, 'w') { |f| f.write JSON.pretty_generate(@cache) }
10
+ end
11
+
12
+ def fetch_cache(key)
13
+ @cache ||= File.exist?(cache_path) ? JSON.parse(File.read(@cache_path)) : {}
14
+ @cache[key] if @cache
15
+ end
16
+
17
+ def clear_cache
18
+ File.delete cache_path if options[:clean] && File.exist?(cache_path)
19
+ end
20
+
21
+ def cache_path
22
+ @cache_path ||= File.join options[:path], CACHE_FILE_NAME
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,183 @@
1
+ require 'thor'
2
+ require 'zip/zip'
3
+ require 'pathname'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'faraday'
7
+ require 'io/console'
8
+ require 'zendesk_apps_support'
9
+
10
+ require 'zendesk_apps_tools/command_helpers'
11
+
12
+ module ZendeskAppsTools
13
+ class Command < Thor
14
+ include Thor::Actions
15
+ include ZendeskAppsSupport
16
+ include ZendeskAppsTools::CommandHelpers
17
+
18
+ SHARED_OPTIONS = {
19
+ ['path', '-p'] => './',
20
+ clean: false
21
+ }
22
+
23
+ source_root File.expand_path(File.join(File.dirname(__FILE__), '../..'))
24
+
25
+ desc 'translate SUBCOMMAND', 'Manage translation files', hide: true
26
+ subcommand 'translate', Translate
27
+
28
+ desc 'bump SUBCOMMAND', 'Bump version for app', hide: true
29
+ subcommand 'bump', Bump
30
+
31
+ desc 'new', 'Generate a new app'
32
+ method_option :'iframe-only', type: :boolean,
33
+ default: false,
34
+ desc: 'Create an iFrame Only app template',
35
+ aliases: ['-i', '--v2']
36
+ def new
37
+ enter = ->(variable) { "Enter this app author's #{variable}:\n" }
38
+ invalid = ->(variable) { "Invalid #{variable}, try again:" }
39
+ @author_name = get_value_from_stdin(enter.call('name'),
40
+ error_msg: invalid.call('name'))
41
+ @author_email = get_value_from_stdin(enter.call('email'),
42
+ valid_regex: /^.+@.+\..+$/,
43
+ error_msg: invalid.call('email'))
44
+ @author_url = get_value_from_stdin(enter.call('url'),
45
+ valid_regex: %r{^https?://.+$},
46
+ error_msg: invalid.call('url'),
47
+ allow_empty: true)
48
+ @app_name = get_value_from_stdin("Enter a name for this new app:\n",
49
+ error_msg: invalid.call('app name'))
50
+
51
+ @iframe_location = if options[:'iframe-only']
52
+ iframe_uri_text = 'Enter your iFrame URI or leave it blank to use'\
53
+ " a default local template page:\n"
54
+ value = get_value_from_stdin(iframe_uri_text, allow_empty: true)
55
+ value == '' ? 'assets/iframe.html' : value
56
+ else
57
+ '_legacy'
58
+ end
59
+
60
+ prompt_new_app_dir
61
+
62
+ skeleton = options[:'iframe-only'] ? 'app_template_iframe' : 'app_template'
63
+ is_custom_iframe = options[:'iframe-only'] && @iframe_location != 'assets/iframe.html'
64
+ directory_options = is_custom_iframe ? { exclude_pattern: /iframe.html/ } : {}
65
+ directory(skeleton, @app_dir, directory_options)
66
+ end
67
+
68
+ desc 'validate', 'Validate your app'
69
+ method_options SHARED_OPTIONS
70
+ def validate
71
+ setup_path(options[:path])
72
+ errors = app_package.validate(marketplace: false)
73
+ valid = errors.none?
74
+
75
+ if valid
76
+ app_package.warnings.each { |w| say w.to_s, :yellow }
77
+ say_status 'validate', 'OK'
78
+ else
79
+ errors.each do |e|
80
+ say_status 'validate', e.to_s
81
+ end
82
+ end
83
+
84
+ @destination_stack.pop if options[:path]
85
+ exit 1 unless valid
86
+ true
87
+ end
88
+
89
+ desc 'package', 'Package your app'
90
+ method_options SHARED_OPTIONS
91
+ def package
92
+ return false unless invoke(:validate, [])
93
+
94
+ setup_path(options[:path])
95
+ archive_path = File.join(tmp_dir, "app-#{Time.now.strftime('%Y%m%d%H%M%S')}.zip")
96
+
97
+ archive_rel_path = relative_to_original_destination_root(archive_path)
98
+
99
+ zip archive_path
100
+
101
+ say_status 'package', "created at #{archive_rel_path}"
102
+ true
103
+ end
104
+
105
+ desc 'clean', 'Remove app packages in temp folder'
106
+ method_option :path, default: './', required: false, aliases: '-p'
107
+ def clean
108
+ setup_path(options[:path])
109
+
110
+ return unless File.exist?(Pathname.new(File.join(app_dir, 'tmp')).to_s)
111
+
112
+ FileUtils.rm(Dir["#{tmp_dir}/app-*.zip"])
113
+ end
114
+
115
+ DEFAULT_SERVER_PATH = './'
116
+ DEFAULT_CONFIG_PATH = './settings.yml'
117
+ DEFAULT_SERVER_PORT = 4567
118
+ DEFAULT_APP_ID = 0
119
+
120
+ desc 'server', 'Run a http server to serve the local app'
121
+ method_option :path, default: DEFAULT_SERVER_PATH, required: false, aliases: '-p'
122
+ method_option :config, default: DEFAULT_CONFIG_PATH, required: false, aliases: '-c'
123
+ method_option :port, default: DEFAULT_SERVER_PORT, required: false
124
+ method_option :app_id, default: DEFAULT_APP_ID, required: false
125
+ def server
126
+ setup_path(options[:path])
127
+ manifest = app_package.manifest_json
128
+
129
+ settings_helper = ZendeskAppsTools::Settings.new
130
+
131
+ settings = settings_helper.get_settings_from_file options[:config], manifest['parameters']
132
+
133
+ unless settings
134
+ settings = settings_helper.get_settings_from_user_input self, manifest['parameters']
135
+ end
136
+
137
+ require 'zendesk_apps_tools/server'
138
+ ZendeskAppsTools::Server.tap do |server|
139
+ server.set :port, options[:port]
140
+ server.set :root, options[:path]
141
+ server.set :parameters, settings
142
+ server.set :manifest, manifest['parameters']
143
+ server.set :config, options[:config]
144
+ server.set :app_id, options[:app_id]
145
+ server.run!
146
+ end
147
+ end
148
+
149
+ desc 'create', 'Create app on your account'
150
+ method_options SHARED_OPTIONS
151
+ method_option :zipfile, default: nil, required: false, type: :string
152
+ def create
153
+ clear_cache
154
+ @command = 'Create'
155
+
156
+ unless options[:zipfile]
157
+ app_name = JSON.parse(File.read(File.join options[:path], 'manifest.json'))['name']
158
+ end
159
+ app_name ||= get_value_from_stdin('Enter app name:')
160
+ deploy_app(:post, '/api/v2/apps.json', name: app_name)
161
+ end
162
+
163
+ desc 'update', 'Update app on the server'
164
+ method_options SHARED_OPTIONS
165
+ method_option :zipfile, default: nil, required: false, type: :string
166
+ def update
167
+ clear_cache
168
+ @command = 'Update'
169
+
170
+ app_id = fetch_cache('app_id') || find_app_id
171
+ unless /\d+/ =~ app_id.to_s
172
+ say_error_and_exit "App id not found\nPlease try running command with --clean or check your internet connection"
173
+ end
174
+ deploy_app(:put, "/api/v2/apps/#{app_id}.json", {})
175
+ end
176
+
177
+ protected
178
+
179
+ def setup_path(path)
180
+ @destination_stack << relative_to_original_destination_root(path) unless @destination_stack.last == path
181
+ end
182
+ end
183
+ end