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.
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