metaforce 0.5.0 → 0.5.1

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.
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
1
  source "http://rubygems.org"
2
+ gem 'highline'
3
+ gem 'thor'
2
4
 
3
5
  # Specify your gem's dependencies in metaforce.gemspec
4
6
  gemspec
data/README.md CHANGED
@@ -61,6 +61,10 @@ feature on a new branch, then send me a pull request with a detailed
61
61
  description. Please provide applicable rspec specs.
62
62
 
63
63
  ## Version History
64
+ **HEAD**
65
+
66
+ * Add thor integration.
67
+
64
68
  **0.5.0** (March 23, 2012)
65
69
 
66
70
  * Implemented CRUD calls.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require './lib/metaforce/thor/metaforce'
4
+
5
+ MetaForce.new :shell => Thor::Shell::Color.new
6
+ MetaForce.start
@@ -5,3 +5,5 @@ require 'metaforce/error'
5
5
  require 'metaforce/manifest'
6
6
  require 'metaforce/services'
7
7
  require 'metaforce/metadata'
8
+ require 'metaforce/custom_actions'
9
+ require 'metaforce/login_details'
@@ -2,6 +2,13 @@ module Metaforce
2
2
  module CoreExtensions
3
3
  module String
4
4
 
5
+ def camelcase
6
+ str = dup
7
+ str.gsub!(/^[a-z]|_[a-z]/) { |a| a.upcase }
8
+ str.gsub!('_', '')
9
+ str
10
+ end unless method_defined?(:camelcase)
11
+
5
12
  def lower_camelcase
6
13
  str = dup
7
14
  str.gsub!(/_[a-z]/) { |a| a.upcase }
@@ -0,0 +1,29 @@
1
+ class Thor
2
+ module Actions
3
+ module CustomActions
4
+ require 'highline'
5
+
6
+ def masked_ask(question)
7
+ password = HighLine.new.ask(question) { |q| q.echo = '*' }
8
+ end
9
+
10
+ def spinner(&block)
11
+ return unless block_given?
12
+ chars = %w{ | / - \\ }
13
+
14
+ t = Thread.new { yield if block_given? }
15
+ while t.alive?
16
+ print chars[0]
17
+ sleep 0.1
18
+ print "\b"
19
+
20
+ chars.push chars.shift
21
+ end
22
+
23
+ t.join
24
+ t.value
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,28 @@
1
+ class LoginDetails
2
+ require 'yaml'
3
+
4
+ attr_accessor :username, :password, :security_token, :sandbox, :log
5
+ STORAGE_LOCATION = "#{Dir.pwd}/.git/force.com.config"
6
+
7
+ def initialize(user, pass, security_token, sandbox, log = nil)
8
+ @username = user
9
+ @password = pass
10
+ @security_token = security_token
11
+ @sandbox = sandbox
12
+ @log = log
13
+ end
14
+
15
+ def save!
16
+ File.open(STORAGE_LOCATION, "w+") do |file|
17
+ file.print Marshal::dump(self)
18
+ end
19
+ end
20
+
21
+ def self.load
22
+ $/="---_---" #record separator
23
+ File.open(STORAGE_LOCATION, "r") do |object|
24
+ return Marshal::load(object)
25
+ end
26
+ end
27
+
28
+ end
@@ -178,7 +178,7 @@ module Metaforce
178
178
  :retrieve_request => options[:options] || {}
179
179
  }
180
180
  end
181
- Transaction.retrieval self, response[:retrieve_response][:result][:id]
181
+ Transaction.retrieval(self, response[:retrieve_response][:result][:id])
182
182
  end
183
183
 
184
184
  # Retrieves files specified in the manifest file (package.xml). Specificy any extra options in +options[:options]+.
@@ -20,25 +20,66 @@ module Metaforce
20
20
  :security_token => Metaforce.configuration.security_token
21
21
  } if options.nil?
22
22
  @session = self.login(options[:username], options[:password], options[:security_token])
23
+
24
+ @client = Savon::Client.new File.expand_path("../../../../wsdl/#{Metaforce.configuration.api_version}/partner.xml", __FILE__) do |wsdl|
25
+ wsdl.endpoint = @session[:services_url]
26
+ end
27
+ @client.http.auth.ssl.verify_mode = :none
28
+ @header = {
29
+ "ins0:SessionHeader" => {
30
+ "ins0:sessionId" => @session[:session_id]
31
+ }
32
+ }
23
33
  end
24
34
 
25
35
  # Performs a login and sets @session
26
36
  def login(username, password, security_token=nil)
27
37
  password = "#{password}#{security_token}" unless security_token.nil?
28
- client = Savon::Client.new File.expand_path("../../../../wsdl/#{Metaforce.configuration.api_version}/partner.xml", __FILE__) do |wsdl|
38
+ @client = Savon::Client.new File.expand_path("../../../../wsdl/#{Metaforce.configuration.api_version}/partner.xml", __FILE__) do |wsdl|
29
39
  wsdl.endpoint = wsdl.endpoint.to_s.sub(/login/, 'test') if Metaforce.configuration.test
30
40
  Metaforce.log("Logging in via #{wsdl.endpoint.to_s}")
31
41
  end
32
- client.http.auth.ssl.verify_mode = :none
42
+ @client.http.auth.ssl.verify_mode = :none
33
43
 
34
- response = client.request(:login) do
44
+ response = @client.request(:login) do
35
45
  soap.body = {
36
46
  :username => username,
37
47
  :password => password
38
48
  }
39
49
  end
40
50
  { :session_id => response.body[:login_response][:result][:session_id],
41
- :metadata_server_url => response.body[:login_response][:result][:metadata_server_url] }
51
+ :metadata_server_url => response.body[:login_response][:result][:metadata_server_url],
52
+ :services_url => response.body[:login_response][:result][:server_url] }
53
+ end
54
+
55
+ # Returns the layout metadata for the sobject.
56
+ # If a +record_type_id+ is passed in, it will only return the layout for
57
+ # that record type.
58
+ #
59
+ # This method is really useful finding out picklist values that are
60
+ # available for a certain record type
61
+ #
62
+ # == Examples
63
+ #
64
+ # @picklists_for_record_type = client.describe_layout('Account', '0123000000100Rn')[:record_type_mappings][:picklists_for_record_type]
65
+ #
66
+ # def picklist_values_for(field)
67
+ # picklist_values = @picklists_for_record_type.select { |f| f[:picklist_name] == field }.first[:picklist_values]
68
+ # picklist_values.select { |p| p[:active] }.collect { |p| [ p[:label], p[:value] ] }
69
+ # end
70
+ #
71
+ # picklist_values_for('some_field__c')
72
+ # # => [ ['label1', 'value1'], ['label2', 'value2'] ]
73
+ def describe_layout(sobject, record_type_id=nil)
74
+ body = {
75
+ 'sObjectType' => sobject
76
+ }
77
+ body['recordTypeID'] = record_type_id if record_type_id
78
+ response = @client.request(:describe_layout) do |soap|
79
+ soap.header = @header
80
+ soap.body = body
81
+ end
82
+ response.body[:describe_layout_response][:result]
42
83
  end
43
84
  end
44
85
  end
@@ -0,0 +1,117 @@
1
+ require 'thor' #should probably be in say, lib/metaforce.rb, but I don't want to make that decision alone.
2
+
3
+ class MetaForce < Thor
4
+ include Thor::Actions
5
+ require './lib/metaforce/custom_actions'
6
+ require './lib/metaforce/login_details'
7
+ require './lib/metaforce'
8
+ include Thor::Actions::CustomActions
9
+
10
+ ######## Tasks ############################################################
11
+ desc "login", "accepts login parameters and completes a soap call to salesforce via the partner api."
12
+ method_option :reset, :type => :boolean, :aliases => "-r", :required => false, :banner => " Reset stored login information"
13
+ def login
14
+ if ((File.exists? LoginDetails::STORAGE_LOCATION) && (options[:reset].nil?))
15
+ say "Using stored login information"
16
+ @login_details = LoginDetails.load
17
+ else
18
+ login_email = ask "Login Email address: "
19
+ sandbox = yes? "Is this a sandbox org login: "
20
+ login_pass = masked_ask "Login Password: "
21
+ login_security_token = masked_ask "Security Token: "
22
+ say "If you'd like I can save this login information to this directories .git/config/force.com.config"
23
+ say "----- PLEASE NOTE, HOWEVER, THAT SAVING THIS INFORMATION IS INSECURE AND IS IN NO WAY ENCRYPTED"
24
+ save = yes? "Should I save this Login information? (y/n) "
25
+
26
+ if save
27
+ @login_details = LoginDetails.new(login_email, login_pass, login_security_token, sandbox)
28
+ @login_details.save!
29
+ else
30
+ @login_details = LoginDetails.new(login_email, login_pass, login_security_token, sandbox)
31
+ end
32
+ end
33
+
34
+ raise "Failed to find viable login information!" if @login_details.nil?
35
+ Metaforce.log = @login_details.log
36
+ Metaforce.configuration.test = @login_details.sandbox
37
+ @client = Metaforce::Metadata::Client.new :username => @login_details.username,
38
+ :password => @login_details.password,
39
+ :security_token => @login_details.security_token
40
+ end
41
+
42
+ ######## Deploy ###########################################################
43
+ desc "deploy", "deploys the current working directory's src folder to salesforce"
44
+ method_option :dir, :type => :string, :aliases => "-d", :required => true, :default => "src", :banner => "Specify the directory to deploy"
45
+ method_option :reset, :type => :boolean, :aliases => "-r", :required => false, :banner => " Reset stored login information"
46
+ def deploy
47
+ login unless @client
48
+ login unless options[:reset].nil?
49
+ raise "failed to create connection!" unless @client
50
+ deploy_results = @client.deploy(options[:dir]).result
51
+ say "Deploy Successful!", color = Thor::Shell::Color::GREEN if deploy_results[:success]
52
+ end
53
+
54
+ ######## RunTests #########################################################
55
+ desc "test", "runs _all_ salesforce unit tests!"
56
+ method_option :dir, :type => :string, :aliases => "-d", :required => true, :default => "src", :banner => "Specify the directory to execute tests"
57
+ method_option :reset, :type => :boolean, :aliases => "-r", :required => false, :banner => " Reset stored login information"
58
+ def test
59
+ login unless @client
60
+ login unless options[:reset].nil?
61
+ raise "failed to create connection!" unless @client
62
+ result = spinner {
63
+ result = @client.deploy(options[:dir], :options => { :run_all_tests => true }).result
64
+ }
65
+ failures = result[:run_test_result][:failures]
66
+ if failures
67
+ failures = [failures] unless failures.responds_to? :each
68
+ say "--- FAILURES: ", color = Thor::Shell::Color::RED
69
+ failures.each_with_index do |f,i|
70
+ say "#{"-" * 80}", color = Thor::Shell::Color::YELLOW
71
+ say ""
72
+ say "\t(#{index +1}) #{failure[:method_name]}"
73
+ say "\t\t#{failure[:message]}", color = Thor::Shell::Color::RED
74
+ say ""
75
+ say "\t\t#{failure[:stack_trace]}", color => Thor::Shell::Color::MAGENTA
76
+ say ""
77
+ end
78
+ end
79
+
80
+ color = (failures) ? Thor::Shell::Color::RED : Thor::Shell::Color::GREEN
81
+ say "Finished in #{Float(result[:run_test_result][:total_time]) / 100} seconds"
82
+ say "#{result[:run_test_result][:num_tests_run]} tests, #{result[:run_test_result][:num_failures]} failures", color = color
83
+ end
84
+
85
+ ######## Pull #############################################################
86
+ desc "pull", "Pull all MetaData objects specified in the package.xml file"
87
+ method_option :reset, :type => :boolean, :aliases => "-r", :required => false, :banner => " Reset stored login information"
88
+ method_option :manifest, :type => :string, :aliases => "-m", :required => true, :banner => " Path to Manifest.xml", :default => "src/package.xml"
89
+ method_option :dir, :type => :string, :aliases => "-d", :required => false, :banner => " Download to directory", :default => "retrieved"
90
+ def pull
91
+ login unless @client
92
+ login unless options[:reset].nil?
93
+ raise "failed to create connection!" unless @client
94
+ result = spinner {
95
+ @client.retrieve_unpackaged(options[:manifest]).to(options[:dir])
96
+ }
97
+ say "Files retrieved sucessfully to #{@directory}", color = Thor::Shell::Color::GREEN
98
+ end
99
+
100
+ ######## Clone ############################################################
101
+ desc "clone", "Pull all MetaData objects from the current org"
102
+ method_option :reset, :type => :boolean, :aliases => "-r", :required => false, :banner => " Reset stored login information"
103
+ method_option :dir, :type => :string, :aliases => "-d", :required => true, :banner => " Download to directory", :default => "retrieved"
104
+ def clone
105
+ login unless @client
106
+ login unless options[:reset].nil?
107
+ raise "failed to create connection!" unless @client
108
+ result = spinner {
109
+ metadata_objects = Hash.new
110
+ @client.metadata_objects.collect { |t| metadata_objects[t[:xml_name].underscore.to_sym] = ["*"] }
111
+ md = Metaforce::Manifest.new(metadata_objects)
112
+ @client.retrieve_unpackaged(md).to(options[:dir])
113
+ }
114
+ say "Files retrieved sucessfully to #{options[:dir]}", color = Thor::Shell::Color::GREEN
115
+ end
116
+
117
+ end
@@ -32,10 +32,10 @@ module Metaforce
32
32
  :folder => "objects",
33
33
  :plural => :action_overrides
34
34
  },
35
- :analytics_snapshot => {
35
+ :analytic_snapshot => {
36
36
  :name => "AnalyticsSnapshot",
37
37
  :folder => "analyticsnapshots",
38
- :plural => :analytics_snapshots
38
+ :plural => :analytic_snapshots
39
39
  },
40
40
  :apex_class => {
41
41
  :name => "ApexClass",
@@ -142,6 +142,11 @@ module Metaforce
142
142
  :folder => "homePageComponents",
143
143
  :plural => :home_page_components
144
144
  },
145
+ :home_page_layout => {
146
+ :name => "HomePageLayout",
147
+ :folder => "HomePageLayouts",
148
+ :plural => :home_page_layouts
149
+ },
145
150
  :layout => {
146
151
  :name => "Layout",
147
152
  :folder => "layouts",
@@ -197,10 +202,10 @@ module Metaforce
197
202
  :folder => "reportTypes",
198
203
  :plural => :report_types
199
204
  },
200
- :scontroler => {
201
- :name => "Scontroler",
205
+ :scontrol => {
206
+ :name => "scontrol",
202
207
  :folder => "scontrols",
203
- :plural => :scontrolers
208
+ :plural => :scontrols
204
209
  },
205
210
  :sharing_reason => {
206
211
  :name => "SharingReason",
@@ -1,3 +1,3 @@
1
1
  module Metaforce
2
- VERSION = "0.5.0"
2
+ VERSION = "0.5.1"
3
3
  end
@@ -5,8 +5,8 @@ require "metaforce/version"
5
5
  Gem::Specification.new do |s|
6
6
  s.name = "metaforce"
7
7
  s.version = Metaforce::VERSION
8
- s.authors = ["Eric J. Holmes"]
9
- s.email = ["eric@ejholmes.net"]
8
+ s.authors = ["Eric J. Holmes", "Kevin J. Poorman"]
9
+ s.email = ["eric@ejholmes.net", "Kevinp@madronasg.com"]
10
10
  s.homepage = "https://github.com/ejholmes/metaforce"
11
11
  s.summary = %q{A Ruby gem for interacting with the Salesforce Metadata API}
12
12
  s.description = %q{A Ruby gem for interacting with the Salesforce Metadata API}
@@ -34,7 +34,8 @@ describe Metaforce do
34
34
  savon.expects(:login).with(:username => 'valid', :password => 'password').returns(:success)
35
35
  session = Metaforce::Services::Client.new.session
36
36
  session.should eq({ :session_id => "00DU0000000Ilbh!AQoAQHVcube9Z6CRlbR9Eg8ZxpJlrJ6X8QDbnokfyVZItFKzJsLHIRGiqhzJkYsNYRkd3UVA9.s82sbjEbZGUqP3mG6TP_P8",
37
- :metadata_server_url => "https://na12-api.salesforce.com/services/Soap/m/23.0/00DU0000000Albh" })
37
+ :metadata_server_url => "https://na12-api.salesforce.com/services/Soap/m/23.0/00DU0000000Albh",
38
+ :services_url => "https://na12-api.salesforce.com/services/Soap/u/23.0/00DU0000000Ilbh" })
38
39
  end
39
40
 
40
41
  it "allows you to set the credentials via the configure block" do
@@ -16,7 +16,8 @@ describe Metaforce::Services::Client do
16
16
  savon.expects(:login).with(:username => 'valid', :password => 'password').returns(:success)
17
17
  session = Metaforce::Services::Client.new(:username => 'valid', :password => 'password').session
18
18
  session.should eq({ :session_id => "00DU0000000Ilbh!AQoAQHVcube9Z6CRlbR9Eg8ZxpJlrJ6X8QDbnokfyVZItFKzJsLHIRGiqhzJkYsNYRkd3UVA9.s82sbjEbZGUqP3mG6TP_P8",
19
- :metadata_server_url => "https://na12-api.salesforce.com/services/Soap/m/23.0/00DU0000000Albh" })
19
+ :metadata_server_url => "https://na12-api.salesforce.com/services/Soap/m/23.0/00DU0000000Albh",
20
+ :services_url=>"https://na12-api.salesforce.com/services/Soap/u/23.0/00DU0000000Ilbh" })
20
21
  end
21
22
 
22
23
  end
@@ -25,7 +26,8 @@ describe Metaforce::Services::Client do
25
26
  savon.expects(:login).with(:username => 'valid', :password => 'password').returns(:success)
26
27
  session = Metaforce::Services::Client.new("username" => 'valid', "password" => 'password').session
27
28
  session.should eq({ :session_id => "00DU0000000Ilbh!AQoAQHVcube9Z6CRlbR9Eg8ZxpJlrJ6X8QDbnokfyVZItFKzJsLHIRGiqhzJkYsNYRkd3UVA9.s82sbjEbZGUqP3mG6TP_P8",
28
- :metadata_server_url => "https://na12-api.salesforce.com/services/Soap/m/23.0/00DU0000000Albh" })
29
+ :metadata_server_url => "https://na12-api.salesforce.com/services/Soap/m/23.0/00DU0000000Albh",
30
+ :services_url=>"https://na12-api.salesforce.com/services/Soap/u/23.0/00DU0000000Ilbh" })
29
31
  end
30
32
  end
31
33
  end
metadata CHANGED
@@ -1,19 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metaforce
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
8
8
  - Eric J. Holmes
9
+ - Kevin J. Poorman
9
10
  autorequire:
10
11
  bindir: bin
11
12
  cert_chain: []
12
- date: 2012-03-23 00:00:00.000000000 Z
13
+ date: 2012-06-02 00:00:00.000000000 Z
13
14
  dependencies:
14
15
  - !ruby/object:Gem::Dependency
15
16
  name: nokogiri
16
- requirement: &70320535767740 !ruby/object:Gem::Requirement
17
+ requirement: &70207844341860 !ruby/object:Gem::Requirement
17
18
  none: false
18
19
  requirements:
19
20
  - - ~>
@@ -21,10 +22,10 @@ dependencies:
21
22
  version: 1.5.0
22
23
  type: :runtime
23
24
  prerelease: false
24
- version_requirements: *70320535767740
25
+ version_requirements: *70207844341860
25
26
  - !ruby/object:Gem::Dependency
26
27
  name: savon
27
- requirement: &70320535767240 !ruby/object:Gem::Requirement
28
+ requirement: &70207844341360 !ruby/object:Gem::Requirement
28
29
  none: false
29
30
  requirements:
30
31
  - - ~>
@@ -32,10 +33,10 @@ dependencies:
32
33
  version: 0.9.7
33
34
  type: :runtime
34
35
  prerelease: false
35
- version_requirements: *70320535767240
36
+ version_requirements: *70207844341360
36
37
  - !ruby/object:Gem::Dependency
37
38
  name: rubyzip
38
- requirement: &70320535744500 !ruby/object:Gem::Requirement
39
+ requirement: &70207844340900 !ruby/object:Gem::Requirement
39
40
  none: false
40
41
  requirements:
41
42
  - - ~>
@@ -43,10 +44,10 @@ dependencies:
43
44
  version: 0.9.5
44
45
  type: :runtime
45
46
  prerelease: false
46
- version_requirements: *70320535744500
47
+ version_requirements: *70207844340900
47
48
  - !ruby/object:Gem::Dependency
48
49
  name: term-ansicolor
49
- requirement: &70320535744120 !ruby/object:Gem::Requirement
50
+ requirement: &70207844340520 !ruby/object:Gem::Requirement
50
51
  none: false
51
52
  requirements:
52
53
  - - ! '>='
@@ -54,10 +55,10 @@ dependencies:
54
55
  version: '0'
55
56
  type: :runtime
56
57
  prerelease: false
57
- version_requirements: *70320535744120
58
+ version_requirements: *70207844340520
58
59
  - !ruby/object:Gem::Dependency
59
60
  name: rake
60
- requirement: &70320535743580 !ruby/object:Gem::Requirement
61
+ requirement: &70207844340060 !ruby/object:Gem::Requirement
61
62
  none: false
62
63
  requirements:
63
64
  - - ! '>='
@@ -65,10 +66,10 @@ dependencies:
65
66
  version: '0'
66
67
  type: :development
67
68
  prerelease: false
68
- version_requirements: *70320535743580
69
+ version_requirements: *70207844340060
69
70
  - !ruby/object:Gem::Dependency
70
71
  name: rspec
71
- requirement: &70320535743020 !ruby/object:Gem::Requirement
72
+ requirement: &70207708128940 !ruby/object:Gem::Requirement
72
73
  none: false
73
74
  requirements:
74
75
  - - ! '>='
@@ -76,10 +77,10 @@ dependencies:
76
77
  version: '0'
77
78
  type: :development
78
79
  prerelease: false
79
- version_requirements: *70320535743020
80
+ version_requirements: *70207708128940
80
81
  - !ruby/object:Gem::Dependency
81
82
  name: mocha
82
- requirement: &70320535742560 !ruby/object:Gem::Requirement
83
+ requirement: &70207708128520 !ruby/object:Gem::Requirement
83
84
  none: false
84
85
  requirements:
85
86
  - - ! '>='
@@ -87,10 +88,10 @@ dependencies:
87
88
  version: '0'
88
89
  type: :development
89
90
  prerelease: false
90
- version_requirements: *70320535742560
91
+ version_requirements: *70207708128520
91
92
  - !ruby/object:Gem::Dependency
92
93
  name: savon_spec
93
- requirement: &70320535742000 !ruby/object:Gem::Requirement
94
+ requirement: &70207708128020 !ruby/object:Gem::Requirement
94
95
  none: false
95
96
  requirements:
96
97
  - - ~>
@@ -98,11 +99,13 @@ dependencies:
98
99
  version: 0.1.6
99
100
  type: :development
100
101
  prerelease: false
101
- version_requirements: *70320535742000
102
+ version_requirements: *70207708128020
102
103
  description: A Ruby gem for interacting with the Salesforce Metadata API
103
104
  email:
104
105
  - eric@ejholmes.net
105
- executables: []
106
+ - Kevinp@madronasg.com
107
+ executables:
108
+ - metaforce
106
109
  extensions: []
107
110
  extra_rdoc_files: []
108
111
  files:
@@ -112,11 +115,14 @@ files:
112
115
  - Guardfile
113
116
  - README.md
114
117
  - Rakefile
118
+ - bin/metaforce
115
119
  - lib/metaforce.rb
116
120
  - lib/metaforce/config.rb
117
121
  - lib/metaforce/core_extensions.rb
118
122
  - lib/metaforce/core_extensions/string.rb
123
+ - lib/metaforce/custom_actions.rb
119
124
  - lib/metaforce/error.rb
125
+ - lib/metaforce/login_details.rb
120
126
  - lib/metaforce/manifest.rb
121
127
  - lib/metaforce/metadata.rb
122
128
  - lib/metaforce/metadata/client.rb
@@ -131,6 +137,7 @@ files:
131
137
  - lib/metaforce/services/client.rb
132
138
  - lib/metaforce/tasks/README.md
133
139
  - lib/metaforce/tasks/metaforce.rake
140
+ - lib/metaforce/thor/metaforce.rb
134
141
  - lib/metaforce/types.rb
135
142
  - lib/metaforce/version.rb
136
143
  - metaforce.gemspec