sfdx プラグインを作成してみる
sfdx のインストール
まずは NodeJs をインストールするところから。
下記リンクの中で、Chocolaty を使ったインストールをしている。
この状態で
> nvm list available | CURRENT | LTS | OLD STABLE | OLD UNSTABLE | |--------------|--------------|--------------|--------------| | 19.9.0 | 18.15.0 | 0.12.18 | 0.11.16 | | 19.8.1 | 18.14.2 | 0.12.17 | 0.11.15 | ... 中略 This is a partial list. For a complete list, visit https://nodejs.org/en/download/releases
とりあえず LTS 入れておけばいいので
nvm install 18.15.0
と nvm use 18.15.0
を実行すると
> node --version v18.15.0
そしたら npm update -g npm
と打って、NPM を最新化しておこう。
SFDX のインストールはこんな感じ。アップデートも同じコマンドでよい。
上でも下でも好きな方を使えば良い。
尚、下のコマンドをインストールすると、sf
がコマンドインターフェースになる。
> npm install sfdx-cli --global > npm install @salesforce/cli --global
ここでは sf
コマンド前提で話を進める。
sfdx プラグインジェネレータのインストール
この辺からジェネレータをインストールする。
依存するパッケージから
npm install -g yarn
npm install -g typescript
そしたらジェネレータのインストール(ここから GitBash)
sf plugins install @salesforce/plugin-dev
これでプラグイン作成の準備ができた。
最初のプラグイン
sf dev generate plugin
コマンドを打つと、プラグインのテンプレートが作成される。
$ sf dev generate plugin _-----_ | | ╭──────────────────────────╮ |--(o)--| │ Time to build an sf │ `---------´ │ plugin! Version 0.7.0 │ ( _´U`_ ) ╰──────────────────────────╯ /___A___\ / | ~ | __'.___.'__ ´ ` |° ´ Y ` ? Are you building a plugin for an internal Salesforce team? No ? Enter the name of your new plugin: hello-plugin ? Enter a robust description for your plugin: hello world ? Enter the author of the plugin: azalea ? Select the % code coverage do you want to enforce: 0% Cloning into 'D:\OneDrive\workspace\sandbox\sfdxMasterSetupPlugin\Example\hello-plugin'... Initialized empty Git repository in D:/OneDrive/workspace/sandbox/sfdxMasterSetupPlugin/Example/hello-plugin/.git/ force hello-plugin\.nycrc force hello-plugin\package.json Changes to package.json were detected. Running yarn install for you to install the required dependencies. yarn install v1.22.19 [1/5] Validating package.json... [2/5] Resolving packages... [3/5] Fetching packages... [4/5] Linking dependencies... warning " > ts-node@10.9.1" has unmet peer dependency "@types/node@*". warning "@salesforce/dev-scripts > typedoc@0.22.18" has incorrect peer dependency "typescript@4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x || 4.7.x". warning "oclif > yeoman-environment@3.12.1" has unmet peer dependency "mem-fs@^1.2.0 || ^2.0.0". warning "oclif > yeoman-environment@3.12.1" has unmet peer dependency "mem-fs-editor@^8.1.2 || ^9.0.0". [5/5] Building fresh packages... $ yarn husky install yarn run v1.22.19 ... (中略)
すると、hello-plugin
のテンプレートが作成された。
早速使ってみる
$ cd hello-plugin/ $ bin/dev hello world Hello World at Tue Apr 11 2023. $ bin/dev hello world --name Astro Hello Astro at Tue Apr 11 2023. $ bin/dev hello world --help Say hello. USAGE $ sf hello world [--json] [-n <value>] FLAGS -n, --name=<value> [default: World] The name of the person you'd like to say hello to. GLOBAL FLAGS --json Format output as json. DESCRIPTION Say hello. Say hello either to the world or someone you know. EXAMPLES Say hello to the world: $ sf hello world Say hello to someone you know: $ sf hello world --name Astro
いい感じだ。
ソースを眺めるとこんな感じ
ソースを見てみると
messages/hello.world.md
# summary Say hello. # description Say hello either to the world or someone you know. # flags.name.summary The name of the person you'd like to say hello to. # examples - Say hello to the world: <%= config.bin %> <%= command.id %> - Say hello to someone you know: <%= config.bin %> <%= command.id %> --name Astro # info.hello Hello %s at %s.
マークダウンのトップレベルセクションがリソース名かな。
src/commands/hello/world.ts
import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; Messages.importMessagesDirectory(__dirname); const messages = Messages.load('hello-plugin', 'hello.world', [ 'summary', 'description', 'examples', 'flags.name.summary', 'info.hello', ]); export type HelloWorldResult = { name: string; time: string; }; export default class World extends SfCommand<HelloWorldResult> { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples'); public static flags = { name: Flags.string({ char: 'n', summary: messages.getMessage('flags.name.summary'), default: 'World', }), }; public async run(): Promise<HelloWorldResult> { const { flags } = await this.parse(World); const time = new Date().toDateString(); this.log(messages.getMessage('info.hello', [flags.name, time])); return { name: flags.name, time, }; } }
上から順に見ていこう。
まずは単純にリソースの読み込み。
Messages.importMessagesDirectory(__dirname); const messages = Messages.load('hello-plugin', 'hello.world', [ 'summary', 'description', 'examples', 'flags.name.summary', 'info.hello', ]);
hello-plugin
はプラグインの名前で、hello.world
はリソースファイル名かな。
最後の配列で読み込むリソースを指定しているようだ。
public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples');
多分名称が予約されている定数。
ヘルプ表示時に利用される文言らしい。
public static flags = { name: Flags.string({ char: 'n', summary: messages.getMessage('flags.name.summary'), default: 'World', }), };
引数定義だね。
name
引数を、略称 n
で、説明は flags.name.summary
リソース参照。デフォルトは「World
」と読める。
思ったよりシンプルな引数構成だ。
public async run(): Promise<HelloWorldResult> { const { flags } = await this.parse(World); const time = new Date().toDateString(); this.log(messages.getMessage('info.hello', [flags.name, time])); return { name: flags.name, time, }; }
run
というくらいだし、プラグインのエントリポイントだと思われる。
1 コマンド 1 機能と考えればコレが妥当なのかもしれませんね。
フラグをパースして、ログに吐き出すだけと
ちょっといじって反応を見よう
まずはリソースをイジってみる
# summary hello というスクリプト。 マークダウンで書けるというのも興味深い。 # description `hello world` か、任意の人名で応答するスクリプトです。 # flags.name.summary name フラグに任意の名称を設定して、hello 表示しませう。 # examples - hello world を出力します: <%= config.bin %> <%= command.id %> - 任意の人名で hello メッセージを出力します: <%= config.bin %> <%= command.id %> --name Astro # info.hello Hello %s (at %s )
するとこんな感じのヘルプとなった。
$ bin/dev hello world --help hello というスクリプト。 USAGE $ sf hello world [--json] [-n <value>] FLAGS -n, --name=<value> [default: World] name フラグに任意の名称を設定して、hello 表示しませう。 GLOBAL FLAGS --json Format output as json. DESCRIPTION hello というスクリプト。 マークダウンで書けるというのも興味深い。 `hello world` か、任意の人名で応答するスクリプトです。 EXAMPLES hello world を出力します: $ sf hello world 任意の人名で hello メッセージを出力します: $ sf hello world --name Astro
summary
には1行しか適用されない縛りみたいなものがありそうだ。FLAGS
はおそらくソースと組み合わせでの出力だろう。GLOBAL FLAGS
は全プラグイン標準搭載と思われる。EXAMPLES
はリソースのexamples
をテンプレートに動作してるようだ。
次はソースもイジってみる。
import { SfCommand, Flags } from '@salesforce/sf-plugins-core'; import { Messages } from '@salesforce/core'; Messages.importMessagesDirectory(__dirname); const messages = Messages.load('hello-plugin', 'hello.world', [ 'summary', 'description', 'examples', 'flags.name.summary', 'info.hello', ]); export type HelloWorldResult = { who: string; time: string; }; export default class World extends SfCommand<HelloWorldResult> { public static readonly summary = messages.getMessage('summary'); public static readonly description = messages.getMessage('description'); public static readonly examples = messages.getMessages('examples'); public static flags = { who: Flags.string({ char: 'w', aliases: ['n', 'name'], summary: messages.getMessage('flags.name.summary'), default: 'World', }), }; public async run(): Promise<HelloWorldResult> { const { flags } = await this.parse(World); const time = new Date().toDateString(); this.log(messages.getMessage('info.hello', [flags.who, time])); return { who: flags.who, time, }; } }
やったことは name
引数を who
に書き換えたものだ。
ヘルプを見ると
USAGE $ sf hello world [--json] [-w <value>] FLAGS -w, --who=<value> [default: World] name フラグに任意の名称を設定して、hello 表示しませう。
思った通り、定義によって生成されているらしい。
aliases
定義は、FragDefinition
を追跡してみたらこんな定義を見つけたので入れてみた。
export type FlagProps = { name: string; char?: AlphabetLowercase | AlphabetUppercase; /** * A short summary of flag usage to show in the flag list. * If not provided, description will be used. */ summary?: string; /** * A description of flag usage. If summary is provided, the description * is assumed to be a longer description and will be shown in a separate * section within help. */ description?: string; /** * The flag label to show in help. Defaults to "[-<char>] --<name>" where -<char> is * only displayed if the char is defined. */ helpLabel?: string; /** * Shows this flag in a separate list in the help. */ helpGroup?: string; /** * Accept an environment variable as input */ env?: string; /** * If true, the flag will not be shown in the help. */ hidden?: boolean; /** * If true, the flag will be required. */ required?: boolean; /** * List of flags that this flag depends on. */ dependsOn?: string[]; /** * List of flags that cannot be used with this flag. */ exclusive?: string[]; /** * Exactly one of these flags must be provided. */ exactlyOne?: string[]; /** * Define complex relationships between flags. */ relationships?: Relationship[]; /** * Make the flag as deprecated. */ deprecated?: true | Deprecation; /** * Alternate names that can be used for this flag. */ aliases?: string[]; /** * Emit deprecation warning when a flag alias is provided */ deprecateAliases?: boolean; /** * Delimiter to separate the values for a multiple value flag. * Only respected if multiple is set to true. Default behavior is to * separate on spaces. */ delimiter?: ','; };
試しに実行してみると
$ bin/dev hello world --who HAHAHA Hello HAHAHA (at Tue Apr 11 2023 ) $ bin/dev hello world --n HAHAHA Hello HAHAHA (at Tue Apr 11 2023 )
思った通りに動いてくれるようだ。
プラグインをインストールしてみる
sf plugins link .
コマンドを実行すると、hello-plugin
を sfdx コマンドとしてインストールし始める。
$ sf plugins link . @salesforce/cli: linking plugin hello-plugin... - [3/5] Fetching packages... warning " > ts-node@10.9.1" has unmet peer dependency "@types/node@*". warning "@salesforce/dev-scripts > typedoc@0.22.18" has incorrect peer dependency "typescript@4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x || 4.6.x || 4.7.x". warning "oclif > yeoman-environment@3.12.1" has unmet peer dependency "mem-fs@^1.2.0 || ^2.0.0". @salesforce/cli: linking plugin hello-plugin... done
実際に動かしてみるか。
$ sf hello world --name Astro Hello Astro (at Tue Apr 11 2023 )
因みにリンクしてるとはパスが通ってるだけっぽいので、
# info.hello Hello Mr, %s (at %s )
こんな修正してやると
$ sf hello world --name Astro Hello Mr, Astro (at Tue Apr 11 2023 )
書き換わった。
リンクの解除は sf plugins unlink .
できるようだ。
コマンドの追加
手動で追加もできそうではあるけど、コマンド生成のジェネレータもあるらしい。
sf dev generate command --name call:external:service