metaforce 0.2.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/.gitignore +7 -0
  2. data/.rvmrc +55 -0
  3. data/Gemfile +10 -0
  4. data/Guardfile +9 -0
  5. data/README.md +59 -0
  6. data/Rakefile +6 -0
  7. data/lib/metaforce.rb +4 -0
  8. data/lib/metaforce/api.rb +3 -0
  9. data/lib/metaforce/api/metadata.rb +117 -0
  10. data/lib/metaforce/api/services.rb +33 -0
  11. data/lib/metaforce/api/transaction.rb +53 -0
  12. data/lib/metaforce/config.rb +24 -0
  13. data/lib/metaforce/manifest.rb +313 -0
  14. data/lib/metaforce/version.rb +3 -0
  15. data/metaforce.gemspec +28 -0
  16. data/spec/.gitignore +1 -0
  17. data/spec/fixtures/package.xml +17 -0
  18. data/spec/fixtures/requests/check_deploy_status/done.xml +33 -0
  19. data/spec/fixtures/requests/check_deploy_status/error.xml +26 -0
  20. data/spec/fixtures/requests/check_status/done.xml +19 -0
  21. data/spec/fixtures/requests/check_status/not_done.xml +19 -0
  22. data/spec/fixtures/requests/deploy/in_progress.xml +13 -0
  23. data/spec/fixtures/requests/describe_metadata/success.xml +230 -0
  24. data/spec/fixtures/requests/list_metadata/objects.xml +33 -0
  25. data/spec/fixtures/requests/login/failure.xml +15 -0
  26. data/spec/fixtures/requests/login/success.xml +39 -0
  27. data/spec/fixtures/sample/src/classes/TestClass.cls +2 -0
  28. data/spec/fixtures/sample/src/classes/TestClass.cls-meta.xml +5 -0
  29. data/spec/fixtures/sample/src/package.xml +8 -0
  30. data/spec/lib/api/metadata_spec.rb +139 -0
  31. data/spec/lib/api/services_spec.rb +24 -0
  32. data/spec/lib/api/transaction_spec.rb +62 -0
  33. data/spec/lib/config_spec.rb +53 -0
  34. data/spec/lib/manifest_spec.rb +181 -0
  35. data/spec/spec_helper.rb +11 -0
  36. data/wsdl/23.0/metadata.xml +3520 -0
  37. data/wsdl/23.0/partner.xml +3190 -0
  38. metadata +167 -0
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ deploy.zip
6
+ retrieve.zip
7
+ test.rb
data/.rvmrc ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ # First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
7
+ environment_id="ruby-1.9.2-p290@metaforce"
8
+
9
+ #
10
+ # Uncomment following line if you want options to be set only for given project.
11
+ #
12
+ # PROJECT_JRUBY_OPTS=( --1.9 )
13
+
14
+ #
15
+ # First we attempt to load the desired environment directly from the environment
16
+ # file. This is very fast and efficient compared to running through the entire
17
+ # CLI and selector. If you want feedback on which environment was used then
18
+ # insert the word 'use' after --create as this triggers verbose mode.
19
+ #
20
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
21
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
22
+ then
23
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
24
+
25
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
26
+ then
27
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
28
+ fi
29
+ else
30
+ # If the environment file has not yet been created, use the RVM CLI to select.
31
+ if ! rvm --create "$environment_id"
32
+ then
33
+ echo "Failed to create RVM environment '${environment_id}'."
34
+ return 1
35
+ fi
36
+ fi
37
+
38
+ #
39
+ # If you use an RVM gemset file to install a list of gems (*.gems), you can have
40
+ # it be automatically loaded. Uncomment the following and adjust the filename if
41
+ # necessary.
42
+ #
43
+ # filename=".gems"
44
+ # if [[ -s "$filename" ]]
45
+ # then
46
+ # rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
47
+ # fi
48
+
49
+ # If you use bundler, this might be useful to you:
50
+ # if command -v bundle && [[ -s Gemfile ]]
51
+ # then
52
+ # bundle install
53
+ # fi
54
+
55
+
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in metaforce.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem "guard"
8
+ gem "guard-rspec"
9
+ gem "growl"
10
+ end
@@ -0,0 +1,9 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ group :specs do
5
+ guard :rspec do
6
+ watch(%r{^spec/.+_spec\.rb$})
7
+ watch(%r{^lib/metaforce/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
8
+ end
9
+ end
@@ -0,0 +1,59 @@
1
+ # Metaforce
2
+ Metaforce is a Ruby gem for interacting with the [Salesforce Metadata API](http://www.salesforce.com/us/developer/docs/api_meta/index.htm).
3
+ The goal of this project is to make the [Migration Tool](http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_deploying_ant.htm) obsolete, favoring Rake over Ant.
4
+
5
+ Metaforce is in active development and is currently in alpha status. Don't use
6
+ it to deploy code to production instances. You've been warned!
7
+
8
+ ## Installation
9
+ ```bash
10
+ gem install metaforce -v '0.2.0.alpha'
11
+ ```
12
+
13
+ ## Usage
14
+ ``` ruby
15
+ client = Metaforce::Metadata::Client.new :username => 'username',
16
+ :password => 'password',
17
+ :security_token => 'security token')
18
+
19
+ client.describe
20
+ # => { :metadata_objects => [{ :child_xml_names => "CustomLabel", :directory_name => "labels" ... }
21
+
22
+ client.list(:type => "CustomObject")
23
+ # => [{ :created_by_id => "005U0000000EGpcIAG", :created_by_name => "Eric Holmes", ... }]
24
+
25
+ deployment = client.deploy(File.expand_path("../src"))
26
+ # => #<Metaforce::Transaction:0x00000102779bf8 @id="04sU0000000WNWoIAO" @type=:deploy>
27
+
28
+ deployment.done?
29
+ # => false
30
+
31
+ deployment.result(:wait_until_done => true)
32
+ # => { :id => "04sU0000000WNWoIAO", :messages => [{ :changed => true ... :success => true }
33
+ ```
34
+
35
+ ## Roadmap
36
+ This gem is far from being feature complete. Here's a list of things that still
37
+ need to be done.
38
+
39
+ * Implement .retrieve for retrieving metadata.
40
+ * Implement CRUD based calls <http://www.salesforce.com/us/developer/docs/api_meta/Content/meta_crud_based_calls_intro.htm>.
41
+ * Implement some helper methods for diffing metadata.
42
+ * And some other stuff that I haven't quite thought of yet...
43
+
44
+ ## License
45
+ Copyright (C) 2012 Eric Holmes
46
+
47
+ This program is free software; you can redistribute it and/or
48
+ modify it under the terms of the GNU General Public License
49
+ as published by the Free Software Foundation; either version 2
50
+ of the License, or (at your option) any later version.
51
+
52
+ This program is distributed in the hope that it will be useful,
53
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
54
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
55
+ GNU General Public License for more details.
56
+
57
+ You should have received a copy of the GNU General Public License
58
+ along with this program; if not, write to the Free Software
59
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Start an irb session"
4
+ task :console do
5
+ sh "irb -I lib -r metaforce"
6
+ end
@@ -0,0 +1,4 @@
1
+ require 'metaforce/version'
2
+ require 'metaforce/config'
3
+ require 'metaforce/manifest'
4
+ require 'metaforce/api'
@@ -0,0 +1,3 @@
1
+ require 'metaforce/api/services'
2
+ require 'metaforce/api/metadata'
3
+ require 'metaforce/api/transaction'
@@ -0,0 +1,117 @@
1
+ require 'metaforce/manifest'
2
+ require 'savon'
3
+ require 'zip/zip'
4
+ require 'base64'
5
+ require 'ostruct'
6
+
7
+ module Metaforce
8
+ module Metadata
9
+ class Client
10
+ DEPLOY_ZIP = 'deploy.zip'
11
+ RETRIEVE_ZIP = 'retrieve.zip'
12
+
13
+ def initialize(options=nil)
14
+ @session = Services::Client.new(options).session
15
+ @client = Savon::Client.new File.expand_path("../../../../wsdl/#{Metaforce.configuration.api_version}/metadata.xml", __FILE__) do |wsdl|
16
+ wsdl.endpoint = @session[:metadata_server_url]
17
+ end
18
+ @header = {
19
+ "ins0:SessionHeader" => {
20
+ "ins0:sessionId" => @session[:session_id]
21
+ }
22
+ }
23
+ end
24
+
25
+ # Specify an array of component types to list
26
+ #
27
+ # example:
28
+ # [
29
+ # { :type => "ApexClass" },
30
+ # { :type => "ApexComponent" }
31
+ # ]
32
+ def list(queries=[])
33
+ unless queries.is_a?(Array)
34
+ queries = [ queries ]
35
+ end
36
+ response = @client.request(:list_metadata) do |soap|
37
+ soap.header = @header
38
+ soap.body = {
39
+ :queries => queries
40
+ }
41
+ end
42
+ response.body[:list_metadata_response][:result]
43
+ end
44
+
45
+ # Describe the organization's metadata
46
+ def describe
47
+ response = @client.request(:describe_metadata) do |soap|
48
+ soap.header = @header
49
+ end
50
+ response.body[:describe_metadata_response][:result]
51
+ end
52
+
53
+ # Checks the status of an async result
54
+ #
55
+ # If type is :retrieve or :deploy, it returns the RetrieveResult or
56
+ # DeployResult, respectively
57
+ def status(ids, type=nil)
58
+ request = "check_status"
59
+ request = "check_#{type.to_s}_status" unless type.nil?
60
+ ids = [ ids ] unless ids.is_a?(Array)
61
+
62
+ response = @client.request(request.to_sym) do |soap|
63
+ soap.header = @header
64
+ soap.body = {
65
+ :ids => ids
66
+ }
67
+ end
68
+ response.body["#{request}_response".to_sym][:result]
69
+ end
70
+
71
+ # Returns true if the deployment with id id is done, false otherwise
72
+ def done?(id)
73
+ self.status(id)[:done]
74
+ end
75
+
76
+ # Deploys dir to the organisation
77
+ def deploy(dir, options={})
78
+ options = OpenStruct.new options
79
+
80
+ if dir.is_a?(String)
81
+ filename = File.join(File.dirname(dir), DEPLOY_ZIP)
82
+ zip_contents = create_deploy_file(filename, dir)
83
+ elsif dir.is_a?(File)
84
+ zip_contents = Base64.encode64(dir.read)
85
+ end
86
+
87
+ yield options if block_given?
88
+
89
+ response = @client.request(:deploy) do |soap|
90
+ soap.header = @header
91
+ soap.body = {
92
+ :zip_file => zip_contents,
93
+ :deploy_options => options.marshal_dump
94
+ }
95
+ end
96
+ Transaction.deployment self, response[:deploy_response][:result][:id]
97
+ end
98
+
99
+ private
100
+
101
+ # Creates the deploy file, reads in the contents and returns the base64
102
+ # encoded data
103
+ def create_deploy_file(filename, dir)
104
+ File.delete(filename) if File.exists?(filename)
105
+ Zip::ZipFile.open(filename, Zip::ZipFile::CREATE) do |zip|
106
+ Dir["#{dir}/**/**"].each do |file|
107
+ zip.add(file.sub(dir + '/', ''), file)
108
+ end
109
+ end
110
+ contents = Base64.encode64(File.open(filename, "rb").read)
111
+ File.delete(filename)
112
+ contents
113
+ end
114
+
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,33 @@
1
+ require 'savon'
2
+
3
+ module Metaforce
4
+ module Services
5
+ class Client
6
+ attr_reader :session
7
+
8
+ def initialize(options=nil)
9
+ options = {
10
+ :username => Metaforce.configuration.username,
11
+ :password => Metaforce.configuration.password,
12
+ :security_token => Metaforce.configuration.security_token
13
+ } if options.nil?
14
+ @session = self.login(options[:username], options[:password], options[:security_token])
15
+ end
16
+
17
+ def login(username, password, security_token=nil)
18
+ password = "#{password}#{security_token}" unless security_token.nil?
19
+ client = Savon::Client.new File.expand_path("../../../../wsdl/#{Metaforce.configuration.api_version}/partner.xml", __FILE__) do |wsdl|
20
+ wsdl.endpoint = wsdl.endpoint.to_s.sub(/login/, 'test') if Metaforce.configuration.test
21
+ end
22
+ response = client.request(:login) do
23
+ soap.body = {
24
+ :username => username,
25
+ :password => password
26
+ }
27
+ end
28
+ { :session_id => response.body[:login_response][:result][:session_id],
29
+ :metadata_server_url => response.body[:login_response][:result][:metadata_server_url] }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,53 @@
1
+ module Metaforce
2
+
3
+ # Convenience class for deployment/retrieval results
4
+ class Transaction
5
+ attr_reader :id
6
+ attr_reader :type
7
+
8
+ def initialize(client, id, type)
9
+ @id = id
10
+ @client = client
11
+ @type = type
12
+ end
13
+
14
+ def self.deployment(client, id)
15
+ self.new client, id, :deploy
16
+ end
17
+
18
+ def self.retrieval(client, id)
19
+ self.new client, id, :retrieve
20
+ end
21
+
22
+ # Returns true if the transaction has completed, false otherwise
23
+ def done?
24
+ @done = @client.done?(@id) unless @done
25
+ @done
26
+ end
27
+ alias :complete? :done?
28
+ alias :completed? :done?
29
+
30
+ # Returns the deploy or retrieve result
31
+ def result(options={})
32
+ self.wait_until_done if options[:wait_until_done]
33
+ raise "Request is not complete! Be sure to call .done? first!" unless @done
34
+ @result = @client.status(@id, @type) if @result.nil?
35
+ @result
36
+ end
37
+
38
+ # Enters a loop until .done? returns true
39
+ def wait_until_done
40
+ max_wait = 30
41
+ wait_time = 1
42
+ until self.done?
43
+ sleep(wait_time)
44
+ if wait_time < 30
45
+ wait_time *= 2
46
+ else
47
+ wait_time = max_wait
48
+ end
49
+ end
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,24 @@
1
+ module Metaforce
2
+ class << self
3
+ def configuration
4
+ @configuration ||= Configuration.new
5
+ end
6
+
7
+ def configure
8
+ yield configuration
9
+ end
10
+ end
11
+
12
+ class Configuration
13
+ attr_accessor :api_version
14
+ attr_accessor :username
15
+ attr_accessor :password
16
+ attr_accessor :security_token
17
+ attr_accessor :test
18
+
19
+ def initialize
20
+ @api_version = "23.0"
21
+ @test = false
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,313 @@
1
+ require 'nokogiri'
2
+
3
+ module Metaforce
4
+ class Manifest
5
+ SFDC_API_VERSION = "23.0"
6
+
7
+ # example format
8
+ # {
9
+ # :apex_class => [
10
+ # "TestController",
11
+ # "TestClass"
12
+ # ],
13
+ # :apex_component => [
14
+ # "SiteLogin"
15
+ # ]
16
+ # }
17
+ def initialize(components={})
18
+ # Map component type => folder
19
+ if components.is_a?(Hash)
20
+ @components = components
21
+ elsif components.is_a?(String)
22
+ @components = {}
23
+ self.parse(components)
24
+ end
25
+ end
26
+
27
+ # Adds components to the package
28
+ def add(type, members=nil)
29
+ unless members.nil?
30
+ @components[type] = [] if @components[type].nil?
31
+ members = [members] if members.is_a?(String)
32
+ members.each do |member|
33
+ member = member.gsub(/.*\//, '').gsub(/\..*/, '');
34
+ @components[type].push(member)
35
+ end
36
+ end
37
+ self
38
+ end
39
+
40
+ # Removes components from the package
41
+ def remove(type, members=nil)
42
+ unless members.nil?
43
+ members = [members] if members.is_a?(String)
44
+ members.each do |member|
45
+ member = member.gsub(/.*\//, '').gsub(/\..*/, '');
46
+ @components[type].delete(member)
47
+ end
48
+ end
49
+ if @components[type].empty?
50
+ @components.delete(type)
51
+ end
52
+ self
53
+ end
54
+
55
+ # Filters the components based on a list of files
56
+ def only(files)
57
+ components = @components
58
+ @components = {}
59
+ files.each do |file|
60
+ parts = file.split('/').last(2)
61
+ folder = parts[0]
62
+ file = parts[1].gsub(/.*\//, '').gsub(/\..*/, '')
63
+ components.each_key do |type|
64
+ if component_folder(type) =~ /#{folder}/i
65
+ unless components[type].index(file).nil?
66
+ self.add(type, file);
67
+ end
68
+ end
69
+ end
70
+ end
71
+ self
72
+ end
73
+
74
+ # Returns the components name
75
+ def component_name(key)
76
+ COMPONENT_TYPE_MAP[key][:name]
77
+ end
78
+
79
+ # Returns the components folder
80
+ def component_folder(key)
81
+ COMPONENT_TYPE_MAP[key][:folder]
82
+ end
83
+
84
+ # Returns a key for the component name
85
+ def component_key(name)
86
+ COMPONENT_TYPE_MAP.each do |key, component|
87
+ return key if component[:name] == name
88
+ end
89
+ end
90
+
91
+ # Returns a string containing a package.xml file
92
+ def to_xml
93
+ xml_builder = Nokogiri::XML::Builder.new do |xml|
94
+ xml.Package("xmlns" => "http://soap.sforce.com/2006/04/metadata") {
95
+ @components.each do |key, members|
96
+ xml.types {
97
+ members.each do |member|
98
+ xml.members member
99
+ end
100
+ xml.name component_name(key)
101
+ }
102
+ end
103
+ xml.version SFDC_API_VERSION
104
+ }
105
+ end
106
+ xml_builder.to_xml
107
+ end
108
+
109
+ def to_hash
110
+ @components
111
+ end
112
+
113
+ def to_package
114
+ components = []
115
+ @components.each do |type, members|
116
+ name = component_name(type)
117
+ components.push({
118
+ :members => members,
119
+ :name => name
120
+ })
121
+ end
122
+ components
123
+ end
124
+
125
+ # Parses a package.xml file
126
+ def parse(file)
127
+ document = Nokogiri::XML(file).remove_namespaces!
128
+ document.xpath('//types').each do |type|
129
+ name = type.xpath('name').first.content
130
+ key = component_key(name);
131
+ type.xpath('members').each do |member|
132
+ if @components[key].is_a?(Array)
133
+ @components[key].push(member.content)
134
+ else
135
+ @components[key] = [member.content]
136
+ end
137
+ end
138
+ end
139
+ self
140
+ end
141
+
142
+ COMPONENT_TYPE_MAP = {
143
+ :action_override => {
144
+ :name => "ActionOverride",
145
+ :folder => "objects"
146
+ },
147
+ :analytics_snapshot => {
148
+ :name => "AnalyticsSnapshot",
149
+ :folder => "analyticsnapshots"
150
+ },
151
+ :apex_class => {
152
+ :name => "ApexClass",
153
+ :folder => "classes"
154
+ },
155
+ :article_type => {
156
+ :name => "ArticleType",
157
+ :folder => "objects"
158
+ },
159
+ :apex_component => {
160
+ :name => "ApexComponent",
161
+ :folder => "components"
162
+ },
163
+ :apex_page => {
164
+ :name => "ApexPage",
165
+ :folder => "pages"
166
+ },
167
+ :apex_trigger => {
168
+ :name => "ApexTrigger",
169
+ :folder => "triggers"
170
+ },
171
+ :business_process => {
172
+ :name => "BusinessProcess",
173
+ :folder => "objects"
174
+ },
175
+ :custom_application => {
176
+ :name => "CustomApplication",
177
+ :folder => "applications"
178
+ },
179
+ :custom_field => {
180
+ :name => "CustomField",
181
+ :folder => "objects"
182
+ },
183
+ :custom_labels => {
184
+ :name => "CustomLabels",
185
+ :folder => "labels"
186
+ },
187
+ :custom_object => {
188
+ :name => "CustomObject",
189
+ :folder => "objects"
190
+ },
191
+ :custom_object_translation => {
192
+ :name => "CustomObjectTranslation",
193
+ :folder => "objectTranslations"
194
+ },
195
+ :custom_page_web_link => {
196
+ :name => "CustomPageWebLink",
197
+ :folder => "weblinks"
198
+ },
199
+ :custom_site => {
200
+ :name => "CustomSite",
201
+ :folder => "sites"
202
+ },
203
+ :custom_tab => {
204
+ :name => "CustomTab",
205
+ :folder => "tabs"
206
+ },
207
+ :dashboard => {
208
+ :name => "Dashboard",
209
+ :folder => "dashboards"
210
+ },
211
+ :data_category_group => {
212
+ :name => "DataCategoryGroup",
213
+ :folder => "datacategorygroups"
214
+ },
215
+ :document => {
216
+ :name => "Document",
217
+ :folder => "document"
218
+ },
219
+ :email_template => {
220
+ :name => "EmailTemplate",
221
+ :folder => "email"
222
+ },
223
+ :entitlement_template => {
224
+ :name => "EntitlementTemplate",
225
+ :folder => "entitlementTemplates"
226
+ },
227
+ :field_set => {
228
+ :name => "FieldSet",
229
+ :folder => "objects"
230
+ },
231
+ :home_page_component => {
232
+ :name => "HomePageComponent",
233
+ :folder => "homePageComponents"
234
+ },
235
+ :layout => {
236
+ :name => "Layout",
237
+ :folder => "layouts"
238
+ },
239
+ :letterhead => {
240
+ :name => "Letterhead",
241
+ :folder => "letterhead"
242
+ },
243
+ :list_view => {
244
+ :name => "ListView",
245
+ :folder => "objects"
246
+ },
247
+ :named_filter => {
248
+ :name => "NamedFilter",
249
+ :folder => "objects"
250
+ },
251
+ :permission_set => {
252
+ :name => "PermissionSet",
253
+ :folder => "permissionsets"
254
+ },
255
+ :portal => {
256
+ :name => "Portal",
257
+ :folder => "portals"
258
+ },
259
+ :profile => {
260
+ :name => "Profile",
261
+ :folder => "profiles"
262
+ },
263
+ :record_type => {
264
+ :name => "RecordType",
265
+ :folder => "objects"
266
+ },
267
+ :remote_site_setting => {
268
+ :name => "RemoteSiteSetting",
269
+ :folder => "remoteSiteSettings"
270
+ },
271
+ :report => {
272
+ :name => "Report",
273
+ :folder => "reports"
274
+ },
275
+ :report_type => {
276
+ :name => "ReportType",
277
+ :folder => "reportTypes"
278
+ },
279
+ :scontroler => {
280
+ :name => "Scontroler",
281
+ :folder => "scontrols"
282
+ },
283
+ :sharing_reason => {
284
+ :name => "SharingReason",
285
+ :folder => "objects"
286
+ },
287
+ :sharing_recalculation => {
288
+ :name => "SharingRecalculation",
289
+ :folder => "objects"
290
+ },
291
+ :static_resource => {
292
+ :name => "StaticResource",
293
+ :folder => "staticResources"
294
+ },
295
+ :translations => {
296
+ :name => "Translations",
297
+ :folder => "translations"
298
+ },
299
+ :validation_rule => {
300
+ :name => "ValidationRule",
301
+ :folder => "objects"
302
+ },
303
+ :weblink => {
304
+ :name => "Weblink",
305
+ :folder => "objects"
306
+ },
307
+ :workflow => {
308
+ :name => "Workflow",
309
+ :folder => "workflows"
310
+ }
311
+ }
312
+ end
313
+ end