コンテンツにスキップ

クイックスタート

このガイドでは、インストールで作成したスタータープロジェクトをベースに、コマンドの追加やビルドなど、Rune における基本的な開発の流れを体験します。

スタータープロジェクトの構造

Section titled “スタータープロジェクトの構造”

create rune-app コマンドにより生成されたプロジェクトは、次のような構成になっています:

  • ディレクトリsrc
    • ディレクトリcommands/
      • hello.ts
      • hello.test.ts
      • ディレクトリtext/
        • _group.ts
        • count.ts
        • count.test.ts
  • package.json
  • tsconfig.json

スターターには、シンプルなトップレベルの hello コマンドに加えて、count サブコマンドをもつ text コマンドグループも含まれています。Rune では src/commands 以下のファイル構成がそのまま CLI のコマンド構造になります。詳しくはルーティングのガイドを参照してください。

スターターに含まれる hello コマンドの中身を見てみましょう。src/commands/hello.ts は以下のようになっています:

src/commands/hello.ts
import { defineCommand } from "@rune-cli/rune";
export default defineCommand({
description: "Say hello from your new Rune CLI",
run({ output }) {
output.log("hello from my-cli");
},
});

defineCommand() にオブジェクトを渡し、返り値をデフォルトエクスポートすることでコマンドを定義します。run 関数がコマンドの実体です。output.log を通じて標準出力に書き込むことで、テスト時に runCommand で出力をキャプチャできるようになります。

インストールのステップで動作確認は済んでいますが、改めて実行してみます:

Terminal window
$ pnpm start hello
hello from my-cli

スターターにはテストファイルも含まれています。src/commands/hello.test.ts を確認してみましょう:

src/commands/hello.test.ts
import { runCommand } from "@rune-cli/rune/test";
import { expect, test } from "vitest";
import helloCommand from "./hello";
test("hello command prints a greeting", async () => {
const result = await runCommand(helloCommand);
expect(result.stdout).toEqual("hello from my-cli\n");
});

@rune-cli/rune/test が提供する runCommand を使うと、子プロセスを起動せずにコマンドをインプロセスで実行し、出力を検証できます。ここではコマンドの標準出力が hello from my-cli であることをテストしています。スターターにはテストフレームワークの Vitest も同梱されているため、すぐにテストを実行できます:

Terminal window
pnpm test

以下のような出力が表示されれば成功です:

RUN v4.1.4 /path/to/project
✓ src/commands/hello.test.ts (1 test) 1ms
✓ src/commands/text/count.test.ts (2 tests) 2ms
Test Files 2 passed (2)
Tests 3 passed (3)
Start at 22:42:44
Duration 96ms (transform 22ms, setup 0ms, import 59ms, tests 3ms, environment 0ms)

上で述べたように、スターターにはトップレベルの hello だけでなく、text というコマンドグループも含まれています。src/commands/text/ のように src/commands 以下にディレクトリを作ると、その名前が複数のコマンドを束ねるコマンドグループとなり、中にあるルーティング対象ファイルがサブコマンドとして公開されます。

このスターターにはすでに src/commands/text/count.ts が含まれているため、text count というコマンドが自動的に有効化されます。src/commands/text/_group.ts では、そのグループの説明文などのメタデータを定義しています。

text count を実行してみましょう。このコマンドは与えられた文字列に含まれる単語の数をカウントします:

Terminal window
$ pnpm start text count "hello rune world"
3

次に、既存の text コマンドグループに transform サブコマンドを追加してみましょう。

src/commands/text/transform.ts を作成し、以下の内容を記述します:

src/commands/text/transform.ts
import { defineCommand } from "@rune-cli/rune";
export default defineCommand({
description: "Transform the input text",
args: [
{
name: "input",
type: "string",
required: true,
},
],
run({ args, output }) {
output.log(args.input.toUpperCase());
},
});

このファイルを src/commands/text/ に追加すると、Rune はこれを text transform コマンドとして認識します。また、args で位置引数を定義しており、run 関数内では args.input として型安全にアクセスできます。

実行してみましょう:

Terminal window
$ pnpm start text transform hello
HELLO

続いて、transform コマンドに --mode オプションを追加してみましょう。これは upper または lower を受け取る enum オプションで、入力テキストを大文字と小文字のどちらに変換するかを切り替えます。options に定義を追加します:

src/commands/text/transform.ts
import { defineCommand } from "@rune-cli/rune";
export default defineCommand({
description: "Transform the input text",
options: [
{
name: "mode",
type: "enum",
values: ["upper", "lower"],
default: "upper",
description: "Transformation to apply",
},
],
args: [
{
name: "input",
type: "string",
required: true,
},
],
run({ args, output }) {
output.log(args.input.toUpperCase());
run({ options, args, output }) {
const result = options.mode === "upper" ? args.input.toUpperCase() : args.input.toLowerCase();
output.log(result);
},
});

--mode lower を指定すると、入力を小文字に変換できます:

Terminal window
$ pnpm start text transform HELLO --mode lower
hello

--help で、オプションや引数の情報が自動的にヘルプに反映されていることも確認できます:

Transform the input text
Usage: my-cli text transform [options] <input>
Options:
--mode <upper|lower> Transformation to apply (default: "upper")
-h, --help Show help
Arguments:
input <string>

hello コマンドのテストと同様に、transform コマンドのテストも書いてみましょう。src/commands/text/transform.test.ts を作成します:

src/commands/text/transform.test.ts
import { runCommand } from "@rune-cli/rune/test";
import { expect, test } from "vitest";
import transformCommand from "./transform";
test("uppercases by default", async () => {
const result = await runCommand(transformCommand, ["hello"]);
expect(result.stdout).toEqual("HELLO\n");
});
test("lowercases with --mode lower", async () => {
const result = await runCommand(transformCommand, ["HELLO", "--mode", "lower"]);
expect(result.stdout).toEqual("hello\n");
});

runCommand の第2引数にコマンドライン引数を配列として渡すことで、実際の CLI 呼び出しと同じ条件でテストできます。テストを実行して結果を確認しましょう:

Terminal window
pnpm test

以下のような出力が表示されれば成功です:

RUN v4.1.4 /path/to/project
✓ src/commands/hello.test.ts (1 test) 1ms
✓ src/commands/text/count.test.ts (2 tests) 2ms
✓ src/commands/text/transform.test.ts (2 tests) 2ms
Test Files 3 passed (3)
Tests 5 passed (5)
Start at 22:42:44
Duration 164ms (transform 56ms, setup 0ms, import 86ms, tests 4ms, environment 0ms)

Rune プロジェクトをビルドすると、配布可能な CLI バイナリとして出力されます。

Terminal window
pnpm build

ビルド成果物は dist/ ディレクトリに出力されます。package.jsonbin フィールドに指定されたエントリーポイント(デフォルトでは dist/cli.mjs)を通じて、npx やグローバルインストール経由で CLI を実行できます。