metaforce 0.2.0.alpha

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