alet 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,41 +7,48 @@ ja:
7
7
  example:
8
8
  default: irbセッションを開始.
9
9
  target_org: SF組織の別名またはログインユーザー名を指定してirbを起動
10
- generate:
11
- desc: Salesforce DXプロジェクトに関連した資材を生成する
12
- project:
10
+ project:
11
+ desc: Salesforce DXプロジェクトの生成・更新を行う
12
+ open_editor: プロジェクト構築/更新完了後、そのプロジェクトをカレントディレクトとしてvs codeを起動する
13
+ generate:
13
14
  desc: salesforce DX projectを生成する
14
15
  target_org: マニフェストファイルとソースファイルの読み込み先となるSF組織の別名またはその組織のユーザー名
15
16
  manifest: マニフェストファイル(package.xml)を生成する
16
17
  retrieve: マニフェストファイルの内容を元にSF組織からソースファイルを取得する
17
- open_editor: プロジェクト構築完了後、そのプロジェクトをカレントディレクトとしてvs codeを起動する
18
18
  example:
19
- default: a Salesfore DXプロジェクトのディレクトリを作成する
19
+ default: Salesfore DXプロジェクトのディレクトリを作成する
20
20
  manifest: プロジェクトディレクトリにマニフェストファイル(package.xml)を含める
21
21
  from_org: 接続組織のメタデータ情報を元にマニフェストファイルの内容を書き込む.
22
22
  retrieve: メタデータのソースファイルをDXプロジェクト内に生成する
23
- connection:
24
- description: 現在のSalesforce組織との接続情報
23
+ update:
24
+ desc: 現在接続中のSF組織の内容でマニフェストとソースファイルを更新する
25
25
  apex:
26
26
  description: Apexコードを実行する
27
- gen:
28
- description: 1つ以上のsObjectクラスを生成する
27
+ model:
28
+ description: 生成済みモデルクラスの表示.またはsobjectクラスの生成
29
+ list:
30
+ noclass: 現セッション内で生成済みのSObjectModelクラスはありません
31
+ title: 【生成済みのSObjectModelクラス】
29
32
  help: |
30
33
  ## Name
31
- gen
34
+ model
32
35
 
33
36
  ## SYNOPSIS
34
- gen sObjectType...
37
+ model [list|load sObjectType...]
35
38
 
36
39
  ## DESCRIPTION
37
- 1つ以上のsObjectクラスを生成する
40
+ 1. セッション内で既に生成済みのsobjectクラスの一覧表示
41
+ 2. 1つ以上のsobjectクラスを生成する
38
42
 
39
43
  ## ARGUMENTS
40
- **sObjectType** sObjectの型名
44
+ **list** sobjectクラスの一覧表示.何も引数を指定しない場合はこの挙動を実行する.
45
+ **load sObjectType...** 1つ以上のsObjectクラスを生成する(sObjectTypeはsobjectの型名)
41
46
 
42
47
  ## EXAMPLES
43
48
  ```shell
44
- gen Account Contact User
49
+ model # 生成済みsobjectクラスの一覧表示
50
+ model list # 同上
51
+ model load Account Contact User # 取引先、取引先責任者およびユーザーオブジェクトのモデルクラスを生成
45
52
  ```
46
53
  query:
47
54
  description: SOQLを使ってレコードを検索する
@@ -103,3 +110,244 @@ ja:
103
110
  ```shell
104
111
  export SELECT Id, Name FROM Account
105
112
  ```
113
+ org:
114
+ description: 現在のSalesforce組織との接続情報
115
+ help: |
116
+ ## NAME
117
+ org
118
+
119
+ ## SYNOPSIS
120
+ org
121
+
122
+ ## DESCRIPTION
123
+ 現在のSalesforce組織との接続情報を表示する
124
+ grep:
125
+ description: オブジェクト(sObject)を探す
126
+ help: |
127
+ ## NAME
128
+ grep
129
+
130
+ ## SYNOPSIS
131
+ grep [-ln] regexp
132
+
133
+ ## DESCRIPTION
134
+ ラベルもしくはAPI名のどちらかでオブジェクト(sObject)の存在を探す。
135
+ sObjectであれば全てが検索対象となる。
136
+
137
+ ## ARGUMENTS
138
+ **regexp** オブジェクトのラベルまたはAPI名を示す正規表現
139
+
140
+ ## OPTIONS
141
+ **-l, [---label]** ラベル名のみで探す
142
+ **-n, [---api-name]** API名のみで探す
143
+
144
+ ## EXAMPLE
145
+ ```shell
146
+ grep Account
147
+ grep -n Account*
148
+ ```
149
+ desc:
150
+ description: オブジェクトの概要を表示する
151
+ table:
152
+ field: 【項目】
153
+ relation: 【参照関係】
154
+ record_type: 【レコードタイプ】
155
+ column:
156
+ label: ラベル
157
+ name: API名
158
+ type: データ型
159
+ relation_name: リレーション名
160
+ relation_type: 種別
161
+ relation_class: クラス名
162
+ relation_field: 項目名
163
+ record_type_name: レコードタイプ名
164
+ record_type_id: レコードタイプID
165
+ record_type_developer_name: developer name
166
+ relation_type:
167
+ parent: 子-親
168
+ child: 親-子
169
+ error:
170
+ notfound: 該当するオブジェクトがありません
171
+ help: |
172
+ ## NAME
173
+ desc
174
+
175
+ ## SYNOPSIS
176
+ desc [-rta] object-name
177
+
178
+ ## DESCRIPTION
179
+ オブジェクトの概要を表示する
180
+
181
+ ## ARGUMENTS
182
+ **object-name** オブジェクト名(API名)
183
+
184
+ ## OPTIONS
185
+ **-r, [---relation]** リレーションを表示
186
+ **-t, [---record-type]** レコードタイプを表示
187
+ **-a, [---all]** 全ての概要を表示
188
+
189
+ もし何もオプションが指定されなければ各項目の概要を表示する
190
+
191
+ ## EXAMPLE
192
+ ```shell
193
+ desc Account # 取引先の項目を表示する
194
+ desc -r Account # 取引先のリレーションを表示する
195
+ ```
196
+ conn:
197
+ description: SF組織に対する接続設定の表示または設定
198
+ invalid_subcommand: 指定されたサブコマンドは利用できません
199
+ help: |
200
+ ## NAME
201
+ conn
202
+
203
+ ## SYNOPSIS
204
+ conn [reset]
205
+
206
+ ## DESCRIPTION
207
+ 引数なしで呼び出したときは各種接続設定を表示する。
208
+ resetサブコマンドが指定されたときは、現在接続しているSF組織への接続を更新する。
209
+ gen:
210
+ description: Salesforce DXプロジェクトの資材を生成する
211
+ error:
212
+ no_subcommand: サブコマンドを指定してください
213
+ invalid_subcommand: 利用できないサブコマンドです
214
+ file_duplicated: 同名のコンポーネントがあります。ファイルを作成できません。
215
+ help: |
216
+ ## NAME
217
+ gen
218
+
219
+ ## SYNOPSIS
220
+ `gen sub-command ...`
221
+
222
+ ## DESCRIPTION
223
+ Salesforce DXプロジェクトの資材を生成する
224
+
225
+ ## ARGUMENTS
226
+ サブコマンドを指定する。サブコマンドごとに必要な引数は異なる。
227
+ **apex** ApexクラスまたはApexトリガーのソースファイルを生成する
228
+ **lwc** LWCのソースファイルを生成する
229
+
230
+ ### apex サブコマンド
231
+
232
+ #### SYNOPSIS
233
+ `gen apex [-t|-e event1,event2,...|-o object-name] name`
234
+
235
+ #### DESCRIPTION
236
+ ApexクラスまたはApexトリガーのソースファイルを生成する。Salesforce DXプロジェクトのルートディレクトリで実行した場合、所定のディレクトリにファイルを生成する.
237
+
238
+ #### ARGUMENTS
239
+ `name` ApexクラスまたはApexトリガーの名前
240
+
241
+ #### OPTIONS
242
+ `-t`, `--trigger` トリガーのソースファイルを生成する
243
+ `-o`, `--sobject` トリガーが機能するオブジェクト
244
+ `-e`, `--event` トリガーが機能するイベント。複数のイベントを指定する場合はカンマでつなぐこと。スペースを入れてはならない(例: bi,bu)。
245
+
246
+ ##### eventで利用可能な値
247
+
248
+ |値|意味|
249
+ |bi|before insert|
250
+ |ai|after insert|
251
+ |bu|before update|
252
+ |au|after update|
253
+ |bd|before delete|
254
+ |ad|after delete|
255
+ |aud|after undelete|
256
+
257
+ #### EXAMPLE
258
+ ```shell
259
+ gen apex MyClass1 # MyClassという名前のApexクラスのソースファイルを生成
260
+ gen apex MyTrigger --trigger # MyTriggerという名前のトリガーのソースファイルを生成
261
+ gen apex -t MyTrigger -o Account # トリガーのオブジェクトにAccount(取引先)オブジェクトを指定
262
+ gen apex -t MyTrigger --event bi,au # トリガーのイベントに before insertとafter update を指定
263
+ ```
264
+
265
+ ### lwc サブコマンド
266
+
267
+ #### SYNOPSIS
268
+ `gen lwc [-l label|-d description|-e|-t target1,target2,...|-o object1,object2,...] name`
269
+
270
+ #### DESCRIPTION
271
+ LWC(Lightning Web Component)を構成するファイルをローカルプロジェクト内に生成する。
272
+ Salesforce DXプロジェクトのルートディレクトリで実行することが前提で、それ以外の場所で実行すると基本的にエラーとなる。
273
+
274
+ #### ARGUMENTS
275
+ `name` LWCの名前
276
+
277
+ #### OPTIONS
278
+ `-l`, `--label` LWC設定ファイルの`masterLabel`セクションの内容を指定する
279
+ `-d`, `--description` LWC設定ファイルの`description`セクションの内容を指定する
280
+ `-e`, `--exposed` LWC設定ファイルの`isExposed`セクションの内容を指定する。デフォルトはfalse。
281
+ `-t`, `--target` LWC設定ファイルの`targets`セクションの内容を指定する。複数設定する場合はカンマ区切りで指定すること。
282
+ `-o`, `--object` LWC設定ファイルの`object`セクションの内容を指定する。`target`に**record**を指定したときにのみ有効。複数設定する場合はカンマ区切りで指定すること。
283
+
284
+ ##### targetオプションの値
285
+
286
+ |値 |意味 |
287
+ |app |lightning__AppPage |
288
+ |flow |lightning__FlowScreen |
289
+ |home |lightning__HomePage |
290
+ |action|lightning__RecordAction|
291
+ |record|lightning__RecordPage |
292
+ |tab |lightning__Tab |
293
+ |bar |lightning__UtilityBar |
294
+
295
+ #### EXAMPLE
296
+ ```shell
297
+ gen lwc LWC1 # LWC1という名前のLWCソースファイルを生成
298
+ gen lwc LWC1 -l "my first LWC"
299
+ gen lwc LWC1 -d "this is my first LWC"
300
+ gen lwc LWC1 -t app,home,record # LWCの利用可能範囲をアプリケーションページ、ホームページ、レコードページに指定
301
+ gen lwc LWC1 -o Account -t record # LWCの利用可能範囲をAccountのレコードページに指定
302
+ ```
303
+
304
+ #### SEE ALSO
305
+ [XML設定ファイルの要素](https://developer.salesforce.com/docs/platform/ja-jp/lwc/guide/reference-configuration-tags.html)
306
+ deploy:
307
+ description: プロジェクトの資材をSalesforce組織にアップロードする
308
+ error:
309
+ no_arguments: 引数を指定してください
310
+ help: |
311
+ ## NAME
312
+ deploy
313
+
314
+ ## SYNOPSIS
315
+ `deploy [-d|-t test1,test2...|-l test-level] component-type component-name...`
316
+
317
+ ## DESCRIPTION
318
+ ローカルプロジェクトの資材をSF組織にアップロードする
319
+
320
+ ## ARGUMENTS
321
+ `component-type` コンポーネントの種類。`file` かそれ以外を指定する。`file`以外で指定できる値は各メタデータタイプ名もしくは短縮名。
322
+ `component-name` コンポーネントの名前。コンポーネントの種類が`file`の場合、この名前はファイルパスまたはディレクトリパスである必要がある。コンポーネントの種類が`file`以外ならワイルドカード(*)も使用できる。
323
+
324
+ ### コンポーネント短縮名の一覧
325
+
326
+ |短縮名|メタデータタイプ名|
327
+ |apex|ApexClass|
328
+ |trigger|ApexTrigger|
329
+ |lwc|LightningComponentBundle|
330
+
331
+ メタデータタイプ名はsfコマンドで確認できる。(`sf org list metadata-type`)
332
+
333
+ ## OPTIONS
334
+ `-d`, `--dryrun` 実際にアップロードはせず、アップロードの検証のみ行う
335
+ `-l`, `--test-level` アップロード時に実行するApexテストの種類を指定する。
336
+ `-t`, `--tests` アップロード時に実行するテスト。`test-level`が`RunSpecifiedTests`のときにのみ有効。複数指定するときはカンマ区切りでつなぐこと。
337
+ `-m`, `--manifest` マニフェストファイルの内容に従ってプロジェクトのファイルをアップロードする。`component-type`が`file`のときのみ有効。このオプションが指定されると`component-name`は無視される。
338
+
339
+ ### test-levelで利用可能な値
340
+
341
+ |値|意味|
342
+ |RunSpecifiedTests|指定された個別のテストのみ実行|
343
+ |RunLocalTests|managed packageを除く組織内のテストを全て実行|
344
+ |RunLocalTests|managed packageを含む全ての組織内テストを実行|
345
+
346
+ ## EXAMPLE
347
+ ```shell
348
+ deploy apex MyClass1
349
+ deploy lwc myLWC
350
+ deploy apex MyClass --test-level RunSpecifiedTests --tests test1,test2
351
+ deploy file -m manifest/package.xml
352
+ deploy file force-app/main/default/class/MyClass1.cls
353
+ ```
data/lib/alet/config.rb CHANGED
@@ -1,18 +1,12 @@
1
1
  module Alet
2
- def self.config
3
- @config ||= Config.new
4
- end
5
-
6
2
  class Config
7
- def connection
8
- @connection
3
+ def org
4
+ @org
9
5
  end
10
- alias conn connection
11
6
 
12
- def connection=(conn)
13
- @connection = conn
7
+ def org=(org)
8
+ @org = org
14
9
  end
15
- alias conn= connection=
16
10
 
17
11
  def cli_options
18
12
  @cli_options ||= {}
@@ -0,0 +1,25 @@
1
+ require_relative '../shared_functions'
2
+
3
+ class Conn < IRB::Command::Base
4
+ category "Alet"
5
+ description t('conn.description')
6
+ help_message TTY::Markdown.parse t('conn.help')
7
+
8
+ def execute(arg)
9
+ argv = arg.split(' ')
10
+ if argv.empty?
11
+ puts '【Current Org settings】'
12
+ show_org_settings
13
+ puts '【Rest Client settings】'
14
+ show_rest_client_settings
15
+ return
16
+ end
17
+
18
+ case argv.first
19
+ when 'reset'
20
+ reset_connection
21
+ else
22
+ puts t('conn.invalid_subcommand')+ ": #{argv.first}"
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,73 @@
1
+ require 'optparse'
2
+ require 'shellwords'
3
+
4
+ class Deploy < IRB::Command::Base
5
+ category "Alet"
6
+ description t('deploy.description')
7
+ help_message TTY::Markdown.parse t('deploy.help')
8
+
9
+ def execute(arg)
10
+ pastel = Pastel.new
11
+ argv = Shellwords.shellsplit(arg)
12
+
13
+ opts = OptionParser.new
14
+ opts.on('-m manifest-file', '--manifest')
15
+ opts.on('-d', '--dryrun')
16
+ opts.on('-l test-level', '--test-level')
17
+ opts.on('-t test1,test2,...', '--tests')
18
+
19
+ if argv.empty?
20
+ puts pastel.red(t('deploy.error.no_arguments'))
21
+ return
22
+ end
23
+
24
+ params = {}
25
+ opts.parse!(argv, into: params)
26
+ component_type = argv.shift
27
+
28
+ options = {
29
+ dry_run: params[:dryrun] || false,
30
+ tests: params[:tests]&.split(','),
31
+ test_level: params[:"test-level"]
32
+ }
33
+
34
+ puts component_type # for debug
35
+ puts argv # for debug
36
+ puts params # for debug
37
+ puts options # for debug
38
+
39
+ if component_type == 'file'
40
+ deploy_file(argv, params[:manifest], options)
41
+ else
42
+ deploy_component(component_type, argv, options)
43
+ end
44
+ rescue => e
45
+ puts pastel.red(e.message)
46
+ end
47
+
48
+ def deploy_file(argv, manifest, options)
49
+ if manifest
50
+ sf.project.deploy_start manifest: manifest, target_org: Alet.config.org.alias, raw_output: true, **options
51
+ else
52
+ sf.project.deploy_start source_dir: argv.first, target_org: Alet.config.org.alias, raw_output: true, **options
53
+ end
54
+ end
55
+
56
+ def deploy_component(component_type, argv, options)
57
+ return if argv.empty?
58
+
59
+ type_alias = {
60
+ 'apex' => 'ApexClass',
61
+ 'trigger' => 'ApexTrigger',
62
+ 'lwc' => 'LightningComponentBundle',
63
+ }
64
+
65
+ metadata_names =
66
+ argv.map do |name|
67
+ metadata_type = type_alias[component_type] || component_type
68
+ %|"#{metadata_type}:#{name}"|
69
+ end
70
+
71
+ sf.project.deploy_start metadata: metadata_names, target_org: Alet.config.org.alias, raw_output: true, **options
72
+ end
73
+ end
@@ -0,0 +1,131 @@
1
+ require 'optparse'
2
+ require 'stringio'
3
+ require 'irb/pager'
4
+ require 'sobject_model/schema'
5
+
6
+ class Describe < IRB::Command::Base
7
+ category "Alet"
8
+ description t('desc.description')
9
+ help_message TTY::Markdown.parse t('desc.help')
10
+
11
+ def execute(arg)
12
+ pastel = Pastel.new
13
+ argv = arg.split(' ')
14
+ opt = OptionParser.new
15
+ opt.on '-r', '--relation'
16
+ opt.on '-t', '--record-type'
17
+ opt.on '-a', '--all'
18
+
19
+ params = {}
20
+ opt.parse(argv, into: params)
21
+
22
+ object_type = argv.first
23
+
24
+ schema = SObjectModel::Schema.new(Alet.describe(object_type.to_sym))
25
+
26
+ if params.has_key?(:relation)
27
+ show_relations(schema)
28
+ elsif params.has_key?(:"record-type")
29
+ show_record_types(schema)
30
+ elsif params.has_key?(:all)
31
+ show_all(schema)
32
+ else
33
+ show_fields(schema)
34
+ end
35
+ rescue SObjectModel::Rest::RequestError => e
36
+ puts pastel.red(e.message)
37
+ rescue SObjectModel::Rest::RecordNotFoundError => e
38
+ puts pastel.red(t('desc.error.notfound'))
39
+ end
40
+
41
+ def show_fields(schema)
42
+ table = create_field_table(schema)
43
+ sio = StringIO.new
44
+ sio << t('desc.table.field')
45
+ sio << "\n"
46
+ sio << table.render(:unicode)
47
+ IRB::Pager.page_content(sio.string)
48
+ end
49
+
50
+ def show_relations(schema)
51
+ table = crate_relation_table(schema)
52
+ sio = StringIO.new
53
+ sio << t('desc.table.relation')
54
+ sio << "\n"
55
+ sio << table.render(:unicode)
56
+ IRB::Pager.page_content(sio.string)
57
+ end
58
+
59
+ def show_record_types(schema)
60
+ table = create_record_type_table(schema)
61
+ sio = StringIO.new
62
+ sio << t('desc.table.record_type')
63
+ sio << "\n"
64
+ sio << table.render(:unicode)
65
+ IRB::Pager.page_content(sio.string)
66
+ end
67
+
68
+ def show_all(schema)
69
+ field_table = create_field_table(schema)
70
+ relation_table = crate_relation_table(schema)
71
+ record_type_table = create_record_type_table(schema)
72
+
73
+ sio = StringIO.new
74
+ sio << t('desc.table.field')
75
+ sio << "\n"
76
+ sio << field_table.render(:unicode)
77
+ sio << "\n"
78
+ sio << "\n"
79
+ sio << t('desc.table.relation')
80
+ sio << "\n"
81
+ sio << relation_table.render(:unicode)
82
+ sio << "\n"
83
+ sio << "\n"
84
+ sio << t('desc.table.record_type')
85
+ sio << "\n"
86
+ sio << record_type_table.render(:unicode)
87
+ IRB::Pager.page_content(sio.string)
88
+ end
89
+
90
+ def create_field_table(schema)
91
+ rows = []
92
+ schema.fields.each do |f|
93
+ if f.picklist_values.empty?
94
+ rows << [f.label, f.name, f.type, '']
95
+ else
96
+ f.picklist_values.each_with_index do |pv, i|
97
+ next unless pv.active
98
+ if i == 0
99
+ rows << [f.label, f.name, f.type, %|#{f.label}/#{pv.value}|]
100
+ else
101
+ rows << ['', '', '', %|#{f.label}/#{pv.value}|]
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ TTY::Table.new(
108
+ [t('desc.column.label'), t('desc.column.name'), t('desc.column.type'), ''],
109
+ rows)
110
+ end
111
+
112
+ def crate_relation_table(schema)
113
+ rows =
114
+ schema.parent_relations.map{|r| [r[:name], t('desc.relation_type.parent'), r[:class_name], r[:field]]} +
115
+ schema.child_relations.map{|r| [r[:name], t('desc.relation_type.child'), r[:class_name], r[:field]]}
116
+
117
+ TTY::Table.new(
118
+ [t('desc.column.relation_name'), t('desc.column.relation_type'), t('desc.column.relation_class'), t('desc.column.relation_field')],
119
+ rows)
120
+ end
121
+
122
+ def create_record_type_table(schema)
123
+ rows = schema.record_types
124
+ .select{|rt| rt.active && rt.available}
125
+ .map{|rt| [rt.name, rt.recordTypeId, rt.developerName]}
126
+
127
+ TTY::Table.new(
128
+ [t('desc.column.record_type_name'), t('desc.column.record_type_id'), t('desc.column.record_type_developer_name')],
129
+ rows)
130
+ end
131
+ end
@@ -14,7 +14,7 @@ class Export < IRB::Command::Base
14
14
 
15
15
  return if soql.nil?
16
16
 
17
- csv = sf.data.query(soql, format: :csv, target_org: ::Alet.config.connection.alias)
17
+ csv = sf.data.query(soql, format: :csv, target_org: ::Alet.config.org.alias)
18
18
 
19
19
  filename = "#{Time.now.strftime('%Y%m%d%H%M%S')}_export.csv"
20
20
  File.open(filename, 'w'){|f| f.write(csv) }
@@ -0,0 +1,36 @@
1
+ def gen_apex(argv, params)
2
+ return if argv.empty?
3
+
4
+ name = argv.first
5
+
6
+ base_dir = Dir.pwd
7
+ dx_dir = 'force-app/main/default'
8
+ dir = if FileTest.exist?(%|#{base_dir}/#{dx_dir}|)
9
+ %|#{base_dir}/#{dx_dir}/#{params[:trigger] ? 'triggers' : 'classes'}|
10
+ else
11
+ base_dir
12
+ end
13
+
14
+ if FileTest.exist?(%|#{dir}/#{name}#{params[:trigger] ? '.trigger' : '.cls'}|)
15
+ pastel = Pastel.new
16
+ puts pastel.red(t('gen.error.file_duplicated'))
17
+ return
18
+ end
19
+
20
+ if params[:trigger]
21
+ event_map = {
22
+ 'bi' => 'before insert',
23
+ 'bu' => 'before update',
24
+ 'bd' => 'before delete',
25
+ 'ai' => 'after insert',
26
+ 'au' => 'after update',
27
+ 'ad' => 'after delete',
28
+ 'aud' => 'after undelete',
29
+ }
30
+ events = params[:event]&.split(',')&.map{|e| event_map[e]}&.compact
31
+ sf.apex.generate_trigger name, output_dir: dir, sobject: params[:sobject], event: events
32
+ else
33
+ sf.apex.generate_class name, output_dir: dir
34
+ sf.apex.generate_class %|#{name}Test|, output_dir: dir, template: :ApexUnitTest
35
+ end
36
+ end