◆ scripts に指定するコマンドで引数を最後じゃなくて間に埋め込みたい
◆ 調べても環境変数を指定するようなやり方
  ◆ 環境変数の使い方はシェルに依存するし 環境変数の設定自体もこの機能でやりたいこと
◆ 自作した

npm-scripts

package.json を

{
"scripts": {
"foo": "echo foo"
}
}

と書くと

yarn run foo

を実行すれば foo に書いたコマンドが実行できます

C:\tmp\a>yarn run foo
yarn run v1.22.4
$ echo foo
foo
Done in 0.11s.

引数を渡すとその後ろに付きます

C:\tmp\a>yarn run foo bar
yarn run v1.22.4
$ echo foo bar
foo bar
Done in 0.09s.

やりたいこと

npm-scripts を多用しているパッケージでは この引数を後ろ以外の場所に埋め込みたいときがときどきあります

例えば foo にこういうコマンドを割り当てる場合に

command1 arg1 arg2 && command2 arg3 arg4

arg1 と arg3 を使う側で指定したいです

yarn run foo bar baz



command1 bar arg2 && command2 baz arg4

のようになってほしいです
これくらい標準でできてほしいのですけど サポートされてないみたいです

ネットで探してみると Stackoverflow では環境変数を使って

{
"foo": "command $FOO arg2"
}
FOO=BAR yarn run foo

という方法が紹介されてるくらいです
環境変数周りは shell 依存になるのでできれば避けたい方法です
npm-scripts は OS や shell 問わず動いてほしいです
そもそも 環境変数を指定するためにこの機能を使いたいことがあるくらいです

{
"start": "mode=$mode node foo.js"
}
yarn run start dev

これで mode に dev を設定する感じです

手動で埋め込む

標準機能にないので自分で用意します

scripts に設定した部分はそのまま呼び出されるので 文字列としていったん別のコマンドに渡して埋め込み処理と実行はそこでやってもらいます

{
"foo": "node scripts/run.js \"node print.js {1} arg2 && node print.js {2} arg4\""
}

これを

yarn run foo bar baz

のように使います
実行されるコマンドはこうなります

node scripts/run.js "node print.js {1} arg2 && node print.js {2} arg4" bar baz

scripts/run.js がいったん実行されるスクリプトです
ここで埋め込みを行ってコマンドを実行します

const [comm, ...args] = process.argv[2].replace(/\{(\d+)\}/g, (_, x) => process.argv[~~x + 2]).trim().split(/\s+/)
require("child_process").spawn(comm, args, { shell: true, stdio: "inherit" })

1つ目の引数の文字列中の 「{1}」 のようなところに残りの引数から埋め込みを行ってます

今回の場合に実行されるコマンドは

node print.js bar arg2 && node print.js baz arg4

となります
print.js は引数を出力する処理にしておきます

[print.js]
console.log(process.argv.slice(2))

これで実行した結果はこうなりました

C:\tmp\a>yarn run foo bar baz
yarn run v1.22.4
$ node scripts/run.js "node print.js {1} arg2 && node print.js {2} arg4" bar baz

[ 'bar', 'arg2' ]
[ 'baz', 'arg4' ]
Done in 0.35s.

見やすくする

動きましたが scripts/run.js をパッケージごとに用意しないといけないです
それに package.json の scripts 中に書くコマンドはエスケープが必要で読むのも書くのもつらいです

なので もう少し見やすく書けるように変更して パッケージ化しました
exsc

インストールすると exsc コマンドが使えるようになるので scripts には exsc コマンドと名前を記述します
また package.json に exsc プロパティを追加して 名前ごとの実行コマンドを記述します

{
"scripts": {
"foo": "exsc foo",
"bar": "exsc bar"
},
"exsc": {
"foo": "node print.js {1} {2} {p1} {p1} {p2} last",
"bar": {
"command": "node print.js {1} && node print.js {a} arg1",
"defaults": {
"1": "111",
"a": "aaa"
}
}
},
"devDependencies": {
"exsc": "gist:cc7ad5bc1a8417b57accb254f31be6a1"
}
}

scripts と exsc で名前を揃えてますが 別に揃える必要はありません
また foo のように直接コマンドを書いても良いですが bar のように command と defaults プロパティを用意してデフォルト値を書くこともできます
あとから考えると scripts 側に引数として書いておけばデフォルト値になるので foo のようなコマンドだけ書ければ十分だったように思います

実行してみた結果です

C:\tmp\a>yarn foo A B --p1 C --p2 D
yarn run v1.22.4
$ exsc foo A B --p1 C --p2 D
[ 'A', 'B', 'C', 'C', 'D', 'last' ]
Done in 0.28s.

{p1} に埋め込むものを指定するには 「--p1 C」 のようになります
「-」 の数は一つ以上ならいくつでも構いません
「--p=X」 形式には対応していません

{1} のように数値のものは 「--1 abc」 のようにも書けますが 「--」 をなしで書けば自動で連番が振られます
この例だと 1 が A で 2 が B になっています

デフォルト値付きの bar はこうなります

C:\tmp\a>yarn run bar
yarn run v1.22.4
warning package.json: No license field
$ exsc bar
[ '111' ]
[ 'aaa', 'arg1' ]
Done in 0.35s.

引数を指定すると

C:\tmp\a>yarn run bar -a AAA
yarn run v1.22.4
warning package.json: No license field
$ exsc bar -a AAA
[ '111' ]
[ 'AAA', 'arg1' ]
Done in 0.36s.

aaa が AAA に変わってますね

foo のほうでも scripts の exsc コマンドのところでデフォルト値を書けます

{
"foo": "exsc foo --p1 xxx -p2 yyy",
}

に変更します

C:\tmp\a>yarn run foo AAA BBB -p2 DDD
yarn run v1.22.4
warning package.json: No license field
$ exsc foo --p1 xxx -p2 yyy AAA BBB -p2 DDD
[ 'AAA', 'BBB', 'xxx', 'xxx', 'DDD', 'last' ]
Done in 0.27s.

p1 はデフォルト値が使われて p2 は指定したほうが使われます