ソース追跡を使ってみた

Salesforce の スクラッチ組織、Developer SandboxとDeveloper Pro Sandboxで利用できるソース追跡を使ってみました。

Spring’21で、スクラッチ組織でしか利用できなかったソース追跡がDeveloper SandboxとDeveloper Pro Sandboxでも利用できるようになったので使ってみました。

目次

ソース追跡を有効化

Sandboxでソース追跡を有効化するには、本番組織での設定が必要です。
本番組織での設定変更後に、作成または更新したSandboxでソース追跡ができるようになります。
(スクラッチ環境ではソース追跡の有効化は不要で、すぐに使えます。)

有効化の手順は、Salesforce DX 開発者ガイドで紹介されています。

Sandbox のソース追跡の有効化

sfdxコマンド

ソース追跡で使用する主なコマンドは以下の3つです。

ローカルで変更してみる

VS Codeで SFDCプロジェクトを作成し、ソース追跡が有効な組織に接続し、ローカルで「myclass01」クラスを作成しました。

>sfdx force:apex:class:create -n myclass01 -d force-app/main/default/classes
target dir = XXXX\force-app\main\default\classes
   create force-app\main\default\classes\myclass01.cls
   create force-app\main\default\classes\myclass01.cls-meta.xml
cmd

プロジェクトには.clsファイルと.cls-meta.xmlが追加されています。

force:source:statusコマンドで変更を確認してみます。

>sfdx force:source:status 
=== Source Status
STATE      FULL NAME  TYPE       PROJECT PATH
─────────  ─────────  ─────────  ─────────────────────────────────────────────────────
Local Add  myclass01  ApexClass  force-app\main\default\classes\myclass01.cls
Local Add  myclass01  ApexClass  force-app\main\default\classes\myclass01.cls-meta.xml
cmd

ローカルで2ファイル追加したことを確認できました。 この変更を force:source:pushコマンドでリモートに反映してみます。

>sfdx force:source:push
*** Deploying with SOAP ***
Job ID | 0Af1m00000SLnB5CAL
SOURCE PROGRESS | | 1/1 Components
=== Pushed Source
STATE  FULL NAME  TYPE       PROJECT PATH
─────  ─────────  ─────────  ─────────────────────────────────────────────────────
Add    myclass01  ApexClass  force-app\main\default\classes\myclass01.cls
Add    myclass01  ApexClass  force-app\main\default\classes\myclass01.cls-meta.xml
cmd

無事デプロイできましたので、再度force:source:statusコマンドで変更を確認してみます。

>sfdx force:source:status
=== Source Status
No results found
cmd

ローカルとリモートで差異がないことを確認できました。

リモートで変更してみる

次に、接続している組織の設定画面から「myclass01」を更新してみました。

force:source:statusコマンドで変更を確認してみます。

>sfdx force:source:status 
Source Status
STATE           FULL NAME  TYPE       PROJECT PATH
──────────────  ─────────  ─────────  ─────────────────────────────────────────────────────
Remote Changed  myclass01  ApexClass  force-app\main\default\classes\myclass01.cls
Remote Changed  myclass01  ApexClass  force-app\main\default\classes\myclass01.cls-meta.xml
cmd

リモートで2ファイル変更されていることを確認できましたので、この変更をforce:source:pull コマンドでローカルに取り込みます。

>sfdx force:source:pull
=== Pulled Source
STATE    FULL NAME  TYPE       PROJECT PATH
───────  ─────────  ─────────  ────────────────────────────────────────────
Changed  myclass01  ApexClass  force-app\main\default\classes\myclass01.cls
cmd

ローカルのmyclass01.cls に、リモートでの変更が反映され、force:source:statuコマンドでも、ローカルとリモートで差異がないことを確認できました。

>sfdx force:source:status
=== Source Status
No results found
cmd

ソース追跡ではそのほかのメタデータの変更も追跡できるので、項目の追加・削除を試してみました。

リモート側で項目を追加し、プロファイル別項目レベルセキュリティ設定やページレイアウトに追加したので、これらの変更をまとめて確認できました。

=== Source Status
STATE          FULL NAME               TYPE         PROJECT PATH
─────────────  ──────────────────────  ───────────  ────────────────────────────────────────────
Remote Add     Admin                   Profile
Remote Add     Account.Field1__c       CustomField
Remote Add     Account-Account Layout  Layout
cmd

これらの変更をforce:source:pullコマンドでローカルに取り込んだ後、リモート側で項目を削除すると、その削除も確認できました。項目追加時と同様に、項目削除でプロファイル別項目レベルセキュリティ設定やページレイアウトも変更されたことがわかります。

>sfdx force:source:status  
=== Source Status
STATE           FULL NAME               TYPE         PROJECT PATH
──────────────  ──────────────────────  ───────────  ──────────────────────────────────────────────────────────────────────
Remote Changed  Admin                   Profile      force-app\main\default\profiles\Admin.profile-meta.xml
Remote Deleted  Account.Field1__c       CustomField  force-app\main\default\objects\Account\fields\Field1__c.field-meta.xml
Remote Changed  Account-Account Layout  Layout       force-app\main\default\layouts\Account-Account Layout.layout-meta.xml
cmd

ソース追跡の裏側

force:source:status コマンドや force:source:push コマンド、force:source:pull コマンドを実行すると、.sfdx/orgs/<接続している組織にログインしているユーザ名>フォルダに MetadataTypeInfos.json と sourcePathInfos.json が生成(または更新)されます。

これらのファイルにソース追跡の情報が保持されているので、中身をのぞいてみます。

MetadataTypeInfos.json

MetadataTypeInfos.jsonには、メタデータの定義が含まれています。

MetadataTypeInfos.jsonはforce:source:statusコマンドを実行すると生成されましたので、 まずは、ローカルで「myclass0」クラスを生成後リモートにpushする前のものを見てみます。

新しく作成した環境で何の変更も加えていなかったので、serverMaxRevisionCounterは0で、メタデータの情報も空でした。

次は、ローカルでの変更を force:source:pushコマンドでリモートに反映したあとのものです。

sourceMembersに"ApexClass__myclass01"が追加され、serverRevisionCounterとlastRetrievedFromServerが同じ値で記載されています。

クラスを追加するとシステム管理者プロファイル「有効な Apex クラス」にも登録されるので、"Profile__Admin"の情報も追加されています。

次は、リモートでの「myclass0」変更後にforce:source:statusコマンドを実行したあとのものです。

serverRevisionCounterの値のみ増えており、ローカルとリモートで差異があることがわかります。 今回はリモート側で2回保存したので、serverRevisionCounterが2つ上がっています。内容は変更せずに保存する空更新でもリビジョンが1つ上がりました。

最後に、リモート側の変更をforce:source:pullコマンドで取り込んだあとのものです。

MetadataTypeInfos.jsonは、force:source:pullコマンド実行でも更新されます。 lastRetrievedFromServerの値が更新されており、ローカルとリモートのリビジョンが一致しました。

なお、maxrevision.jsonの情報は、SourceMemberオブジェクトで保持されおり、以下のクエリで情報を取得できました。

参考:Developers’ Blog : A Deep Dive Into Source Tracked Projects

>sfdx force:data:soql:query --query "SELECT ChangedBy, IsNameObsolete, MemberName, 
MemberType, RevisionCounter FROM SourceMember" --usetoolingapi

CHANGEDBY        ISNAMEOBSOLETE  MEMBERNAME  MEMBERTYPE  REVISIONCOUNTER
───────────────  ──────────────  ──────────  ──────────  ───────────────
0050l000005rwfV  false           Admin       Profile     1
0050l000005rwfV  false           myclass01   ApexClass   4
Total number of records retrieved: 2.
Querying Data... done
cmd

sourcePathInfos.json

sourcePathInfos.jsonには、ローカルのファイル情報が含まれています。

まずは、ローカルで「myclass0」クラスを生成後リモートにpushする前のものを見てみます。

ローカルのプロジェクトフォルダ以下にある情報が記載されています。

次に、ローカルでの変更をforce:source:pushコマンドでリモートに反映したあとのものを見てみました。こちらは「myclass0」クラスに関する部分の差分を確認してみました。

クラス作成後内容を変更していないので、差分はstateが"n"から"u"に変わっただけで、modifiedTime、changeTime、contentHashは同じでした。

次は、リモートでの「myclass0」変更後にforce:source:statusコマンドを実行したあとのものです。

当然の結果ですが、ローカルファイルは更新されていないので、何も変わっていません。

最後に、リモート側の変更をforce:source:pullコマンドで取り込んだあとのものです。

myclass01.clsが更新されたので、ファイルの情報が更新されています。

sourcePathInfos.jsonは、force:source:statusコマンド実行では更新されません。

このコマンドを実行すると、ローカルにあるファイルから現在のハッシュ値を計算し、sourcePathInfos.jsonで保持しているハッシュ値と比較して変更有無が判断されます。sourcePathInfos.jsonで持っているハッシュ値はリモートにpushした時の値なので、ローカルで変更し、リモートにpushすることなくローカルでもとの内容に戻すと、”変更なし”と判断されます。 リモート側で同様の操作をした場合は”変更あり”と判断されるので、注意が必要です。

最後に

ソース追跡では「どこがどう変更されたのか」はわからないので、内容の変更履歴を追跡するにはGitなどが必要です。
また、force:source:deployコマンド及びforce:source:retrieveコマンドを使った場合は変更が追跡されませんので、注意が必要です。

一方、追跡対象を明示することなく(追跡できるものであれば)すべての変更を追跡できるのは便利だと感じました。
これまで「設定」-「設定変更履歴の参照」で組織の変更を確認していたようなケースでは、ソース追跡を使うことで簡単に変更を確認できるようになりますので、ぜひ使ってみてください。