cli チュートリアル

このチュートリアルでは、cli 実行ファイルを使って MoonBit の外からスコアスナップショットを計算する方法を、シェル、インデックスビルダーと同じ Python、Node.js の順に示します。このリポジトリのチェックアウト、MoonBit ツールチェーン、Node.js 20.16、22.3 以降が必要です。

クイックスタート

リポジトリのルートで実行ファイルをビルドします。

moon build src/cli --target js

1 つのパッケージのシグナルをファイルに書きます。

cat > payload.json <<'JSON'
{"dependents": 20, "recent_dependents": 4, "downloads": 300,
 "days_since_release": 40, "historical_dependents": 10,
 "historical_recent_dependents": 2, "historical_downloads": 0,
 "historical_days_since_release": 10}
JSON
node _build/js/debug/build/cli/cli.js score-snapshot --input payload.json

コマンドはスナップショットを 1 行で出力します。

{"score":301.7852882193072,"score_30d_ago":135.2764584196223,"score_growth_30d":166.5088297996849,"score_growth_ratio_30d":1.2308780976744749,"rank_label":"S","momentum_label":"Rising","activity_multiplier":1.06}

これは MoonBit での @score.compute_score_snapshot(20, 4, 300, 40, 10, 2, 0, 10) と同じ数値です。

よくある作業

わからない値は省く

どのキーも省略可能で、既定値は 0 です。5 日前にリリースされ、被依存数が 3 で、ほかは不明なパッケージの場合:

echo '{"dependents": 3, "days_since_release": 5}' > new.json
node _build/js/debug/build/cli/cli.js score-snapshot --input new.json
{"score":59.00068800926255,"score_30d_ago":0,"score_growth_30d":59.00068800926255,"score_growth_ratio_30d":1,"rank_label":"C","momentum_label":"Stable","activity_multiplier":1.12}

過去のシグナルがすべて 0 なので、score_30d_ago は 0 で、成長率は 1 と報告されます。

Python から呼び出す

インデックスビルダーはパッケージごとに 1 回このコマンドを実行します。そのヘルパーの最小版は次のとおりです。

import json, os, subprocess, tempfile

CLI = "_build/js/debug/build/cli/cli.js"

def score_snapshot(signals: dict) -> dict:
    with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as f:
        json.dump(signals, f)
        path = f.name
    try:
        done = subprocess.run(
            ["node", CLI, "score-snapshot", "--input", path],
            check=True, capture_output=True, text=True,
        )
    finally:
        os.unlink(path)
    return json.loads(done.stdout)

print(score_snapshot({"dependents": 20, "recent_dependents": 4,
                      "downloads": 300, "days_since_release": 40})["rank_label"])
S

check=True は 0 以外の終了ステータスをすべて例外にするので、JSON のエラーメッセージと入力ファイルの欠落の両方に対応できます。

Node.js から呼び出す

import { execFileSync } from "node:child_process";
import { writeFileSync } from "node:fs";

writeFileSync("p.json", JSON.stringify({ dependents: 936, days_since_release: 120 }));
const out = execFileSync("node", [
  "_build/js/debug/build/cli/cli.js", "score-snapshot", "--input", "p.json",
]);
console.log(JSON.parse(out).rank_label);
S

乗数が 11 なら、被依存数 936 だけでランク S に届きます。理由は score の設計 で導いています。

エラーを扱う

エラーでは error キーを持つ JSON オブジェクトを出力し、ステータス 1 で終了します。

echo 'not json' > bad.json
node _build/js/debug/build/cli/cli.js score-snapshot --input bad.json; echo "status $?"
{"error":"Failed to parse JSON input"}
status 1

出力を解析する前に終了ステータスを確認してください。ファイルがない場合も 1 で終了しますが、JSON ではなく Node.js のスタックトレースを標準エラーに出力します。

さらに進んで

  • まとめて処理する。 呼び出しのたびに Node.js のプロセスが起動し、そのコストはスコアの計算よりはるかに大きくなります。1 つの MoonBit プログラムで多数のパッケージを扱うなら、score チュートリアル のように @score.compute_score_snapshot を直接呼び出してください。
  • シグナルの出どころ。 インデックスビルダーがこのコマンドを呼ぶ前に被依存数、最近の被依存数、過去の値をどう数えるかは アーキテクチャガイド で説明しています。
  • 入力を厳密にする。 コマンドはいい加減な入力も受け付けます。文字列と null は 0 になり、小数は切り捨てられます。黙って 0 になることでバグが隠れるおそれがあるなら、呼び出し側でペイロードを検証してください。

よくある落とし穴

  • 数値を文字列で書く。 {"dependents": "20"} は被依存数 0 として評価されます。"20" ではなく 20 と書いてください。
  • 引数の順序の誤り。 score-snapshot はスクリプトの直後の引数でなければなりません。--input file.json score-snapshot では使い方のエラーが出力されます。
  • 古いビルド。 インデックスビルダーは既存の cli.js を再利用します。src/score を変更したらビルドし直してください。そうしないとデータベースは古い規則で評価されます。
  • 大きすぎるカウント。 2147483647 以上の数(変換は Int の最大値で飽和します)はスコアを NaN にします。JSON には NaN がないので、出力では数値が入るべき場所に文字列 "NaN" が入ります: {"score":"NaN",...}。

次のステップ

  • cli API: 入力、出力、エラーの正確な約束。
  • cli の設計: 橋渡しがファイルベースのコマンドである理由。
  • score API: 各出力フィールドの意味。