<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>ShiroDoromoto</title>
		<link>https://shiro-doro.site/</link>
		<description>Recent content on ShiroDoromoto</description>
		<generator>Hugo</generator>
		<language>ja</language>
		
		
		
		
			<lastBuildDate>Tue, 16 Jun 2026 00:00:00 +0000</lastBuildDate>
		
			<atom:link href="https://shiro-doro.site/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>Dockerを使わずに SFTP/FTPS クライアントを E2E テストする（Go）</title>
				<link>https://shiro-doro.site/posts/sftp-ftps-e2e-without-docker/</link>
				<pubDate>Tue, 16 Jun 2026 00:00:00 +0000</pubDate>
				<guid>https://shiro-doro.site/posts/sftp-ftps-e2e-without-docker/</guid>
				<description>&lt;p&gt;ある CLI ツールに、ビルド成果物をサーバへアップロードする機能を足しました。転送方式は &lt;strong&gt;SFTP&lt;/strong&gt;（SSH 経由）と &lt;strong&gt;FTPS&lt;/strong&gt;（TLS 付きの FTP）の2つ。動いてほしいのは「接続 → 認証 → ディレクトリ作成 → ファイル転送」という一連の流れなので、関数単体のテストだけでは心もとない。実際にサーバへつないで往復させる、いわゆる E2E テストが欲しくなりました。&lt;/p&gt;&#xA;&lt;p&gt;素直に考えると Docker で &lt;code&gt;vsftpd&lt;/code&gt; や &lt;code&gt;sftpgo&lt;/code&gt; を立てたくなります。でも、やってみる前から気が重い理由がいくつかありました。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;テスト実行に Docker デーモンが前提になる（手元でも CI でも）。&lt;/li&gt;&#xA;&lt;li&gt;FTP の &lt;strong&gt;パッシブモード&lt;/strong&gt; はデータ転送用に別ポートを開く。これがコンテナのポートマッピングと相性が悪く、設定が一気に面倒になる。&lt;/li&gt;&#xA;&lt;li&gt;そもそもテスト1つのためにインフラが散らかる。&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;結論を先に書くと、&lt;strong&gt;サーバ自体を Go のテストプロセス内（in-process）に立てる&lt;/strong&gt;ことで、Docker なしで両方とも E2E テストできました。しかも片方は新しい依存すら増えません。この記事はそのやり方と、途中で踏んだデッドロックの話です。&lt;/p&gt;&#xA;&lt;h2 id=&#34;docker-と-in-process-の比較&#34;&gt;Docker と in-process の比較&lt;/h2&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;Docker（vsftpd 等）&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;in-process Go サーバ&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;デーモン&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;必要&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;不要&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;起動の速さ&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;コンテナ起動待ち&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;プロセス内で即時&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;FTP パッシブポート&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;マッピング設定が必要&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;localhost で無痛&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;実装の手間&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;compose を書く&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;サーバを数十行&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;実サーバ固有の挙動&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;踏める&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;踏めない（後述）&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;最後の行が唯一の弱点で、ここは後ろで触れます。それ以外は in-process が素直でした。&lt;/p&gt;</description>
			</item>
			<item>
				<title>AI が動かす前提で CLI を設計する</title>
				<link>https://shiro-doro.site/posts/cli-for-agents/</link>
				<pubDate>Mon, 15 Jun 2026 15:00:00 +0900</pubDate>
				<guid>https://shiro-doro.site/posts/cli-for-agents/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;シリーズ・第 5 回（最終回）&lt;/strong&gt; — 「個人で静的サイト生成ツールを作って配るまで」（全 5 回）。前回は&lt;a href=&#34;https://shiro-doro.site/posts/brew-scoop-release/&#34;&gt;brew と scoop で配る&lt;/a&gt;話でした。一覧は &lt;a href=&#34;https://shiro-doro.site/tags/crofty/&#34;&gt;シリーズの記事一覧&lt;/a&gt; から。今回のテーマは「AI が動かす前提の CLI 設計」です。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;crofty は、端末から打つ素朴な CLI です。ただ、設計の前提が少しだけ変わっています。&lt;/p&gt;&#xA;&lt;p&gt;「人が crofty を入れて最初の設定をしたら、あとはその人の AI（エージェント）が回す」。そういう使われ方を想定しています。人が毎回コマンドを覚えて打つのではなく、人の代わりにエージェントが crofty を動かす。そのときに困らない作りにしておく、ということです。&lt;/p&gt;&#xA;&lt;p&gt;操作する相手に AI を含めると、CLI の設計はどう変わるか。シリーズ最終回は、その話です。&lt;/p&gt;&#xA;&lt;h2 id=&#34;クリックでなく出力を窓口にする&#34;&gt;クリックでなく、出力を窓口にする&lt;/h2&gt;&#xA;&lt;p&gt;人とエージェントでは、できることが違います。人は画面をクリックでき、ドキュメントを読めます。エージェントはクリックできませんが、コマンドを実行して、その出力を読めます。&lt;/p&gt;&#xA;&lt;p&gt;この違いから、設計の重心が「画面」から「出力テキスト」に移ります。だから crofty は、GUI や管理画面を作りません。代わりに、コマンドの出力を、人にもエージェントにも読める窓口にすることに力を入れています。具体的には、次の 3 つです。&lt;/p&gt;&#xA;&lt;h2 id=&#34;-何ができるかは聞けば分かる&#34;&gt;① 何ができるかは、聞けば分かる&lt;/h2&gt;&#xA;&lt;p&gt;機能をドキュメントの奥に埋めず、ツール自身に聞けるようにします。&lt;/p&gt;&#xA;&lt;p&gt;エージェントが最初に知りたいのは「どんなコマンドがあって、どう使うのか」です。&lt;code&gt;crofty agent&lt;/code&gt; は、その全体像 ― コマンドと引数、使う順番、状態を読むための口 ― を一度にまとめて返します。AI にまず読ませる一枚で、これを実行すれば、あとは自分で動き出せます。&lt;/p&gt;&#xA;&lt;p&gt;そのうえで、目的別の窓口もあります。&lt;code&gt;crofty features&lt;/code&gt; は「何ができて、どう有効にするか」を返します。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;$ crofty features&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;crofty features — what you can do, and how to turn each thing on.&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Out of the box (works in a fresh project):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    rss        an Atom/RSS feed and a &amp;#39;Follow by RSS&amp;#39; link&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;               → automatic — nothing to set&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    share      reader share buttons, and `crofty share` …&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;               → automatic on posts; `crofty share &amp;lt;path&amp;gt;` for authors&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;Restyle (owned, contract-safe):&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    looks      ready-made colour/type presets (quiet-paper, …)&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;               → crofty theme set &amp;lt;name&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;各行に「何ができるか」と「どう有効にするか」がセットで出ます。&lt;code&gt;crofty config&lt;/code&gt; を打てば、「今どうなっているか」も同じように返ります。どれも、人が読んでも AI が読んでも、ドキュメントを探さずに動き出すための窓口です。&lt;/p&gt;</description>
			</item>
			<item>
				<title>自作 CLI を brew と scoop で配る ― GoReleaser でまとめて</title>
				<link>https://shiro-doro.site/posts/brew-scoop-release/</link>
				<pubDate>Mon, 15 Jun 2026 14:00:00 +0900</pubDate>
				<guid>https://shiro-doro.site/posts/brew-scoop-release/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;シリーズ・第 4 回&lt;/strong&gt; — 「個人で静的サイト生成ツールを作って配るまで」（全 5 回予定）。前回は&lt;a href=&#34;https://shiro-doro.site/posts/i18n-design/&#34;&gt;多言語サイトの設計&lt;/a&gt;でした。一覧は &lt;a href=&#34;https://shiro-doro.site/tags/crofty/&#34;&gt;シリーズの記事一覧&lt;/a&gt; から。今回のテーマは「brew と scoop で配る実務」です。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;CLI ができたら、次は「どう配るか」です。&lt;code&gt;go install&lt;/code&gt; でも配れますが、Homebrew や Scoop で入れられた方が、使う人にはずっと楽です。&lt;/p&gt;&#xA;&lt;p&gt;とはいえ、mac・Windows・Linux 向けに手でビルドして、Homebrew の formula と Scoop の manifest を手書きして…は、リリースのたびにやっていられません。そこを GoReleaser に一括でやらせた話です。ひとつ、macOS で素直に詰まったところ（署名）も含めて。&lt;/p&gt;&#xA;&lt;h2 id=&#34;goreleaser-に一括でやらせる&#34;&gt;GoReleaser に一括でやらせる&lt;/h2&gt;&#xA;&lt;p&gt;GoReleaser は、設定ファイル 1 枚から次を全部やってくれます。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;mac・Windows・Linux × amd64・arm64 のバイナリをビルド&lt;/li&gt;&#xA;&lt;li&gt;Homebrew formula と Scoop manifest を生成&lt;/li&gt;&#xA;&lt;li&gt;それらを公開リポジトリ（tap / bucket）へ push&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;リリースは、git tag を切って 1 コマンドです。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;git tag v0.1.0&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;nv&#34;&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;k&#34;&gt;$(&lt;/span&gt;gh auth token&lt;span class=&#34;k&#34;&gt;)&lt;/span&gt; goreleaser release --clean&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;公開前に試すなら、push しない snapshot モードで中身を確認できます。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;goreleaser release --snapshot --clean   &lt;span class=&#34;c1&#34;&gt;# dist/ に生成するだけ。公開はしない&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;バージョンは-git-tag-を単一の出どころに&#34;&gt;バージョンは git tag を単一の出どころに&lt;/h2&gt;&#xA;&lt;p&gt;地味に大事なのが、バージョンの一致です。バイナリが名乗る版・ダウンロード URL・パッケージの版がバラバラだと、事故のもとになります。&lt;/p&gt;</description>
			</item>
			<item>
				<title>多言語サイトの設計 ― テキストを 3 種類に分けて持つ</title>
				<link>https://shiro-doro.site/posts/i18n-design/</link>
				<pubDate>Mon, 15 Jun 2026 13:00:00 +0900</pubDate>
				<guid>https://shiro-doro.site/posts/i18n-design/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;シリーズ・第 3 回&lt;/strong&gt; — 「個人で静的サイト生成ツールを作って配るまで」（全 5 回予定）。前回は&lt;a href=&#34;https://shiro-doro.site/posts/contract-the-output/&#34;&gt;出力を契約する&lt;/a&gt;話でした。一覧は &lt;a href=&#34;https://shiro-doro.site/tags/crofty/&#34;&gt;シリーズの記事一覧&lt;/a&gt; から。今回のテーマは「多言語対応をどう設計するか」です。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;「サイトを多言語にする」と聞くと、まず本文の翻訳を思い浮かべます。でも実際にやってみると、翻訳が要るテキストは本文だけではありませんでした。ナビのラベル、プロフィール、設定値。それぞれ性質が違い、同じ入れ物に押し込むと無理が出ます。&lt;/p&gt;&#xA;&lt;p&gt;うまくいったのは、テキストを種類ごとに分けて、それぞれに合った持ち方をする設計でした。今回はその話です。例として Hugo（crofty が内部で使うもの）を挙げます。&lt;/p&gt;&#xA;&lt;h2 id=&#34;テキストは-3-種類に分かれる&#34;&gt;テキストは 3 種類に分かれる&lt;/h2&gt;&#xA;&lt;p&gt;多言語サイトのテキストを整理すると、だいたい次の 3 種類になります。住む場所も、翻訳の仕組みも別です。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;種類&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;例&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;持ち方&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;本文&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;記事&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;言語ごとのファイル&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;固定文言&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;ナビ、ボタン、「Support」&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;言語 → 文字列の翻訳テーブル&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;設定・データ&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;サイト設定、プロフィール&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;フィールド単位で「共通」か「言語ごと」&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;ひとつずつ見ます。&lt;/p&gt;&#xA;&lt;h2 id=&#34;本文--言語ごとのファイル&#34;&gt;本文 ― 言語ごとのファイル&lt;/h2&gt;&#xA;&lt;p&gt;記事は、ひとつのフォルダ（ページバンドル）に言語別のファイルを並べます。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;content/posts/my-article/&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  index.md       … 日本語（既定言語）&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  index.en.md    … 英語&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  photo.avif     … 同梱する画像&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ビルドすると &lt;code&gt;/posts/my-article/&lt;/code&gt; と &lt;code&gt;/en/posts/my-article/&lt;/code&gt; の両方ができ、言語の切り替えはページ同士のリンクで済みます。JavaScript は要りません。&lt;/p&gt;&#xA;&lt;p&gt;ひとつだけ小さな罠があります。画像（ページではないファイル）は既定言語のパスにしか出力されないので、英語版から相対参照すると 404 になります。英語版だけ、絶対パスで既定言語側を指せば直ります。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-markdown&#34; data-lang=&#34;markdown&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;![&lt;span class=&#34;nt&#34;&gt;図&lt;/span&gt;](&lt;span class=&#34;na&#34;&gt;photo.avif&lt;/span&gt;)                     &amp;lt;!-- 日本語：相対でよい --&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;![&lt;span class=&#34;nt&#34;&gt;図&lt;/span&gt;](&lt;span class=&#34;na&#34;&gt;/posts/my-article/photo.avif&lt;/span&gt;)   &amp;lt;!-- 英語：絶対パスで指す --&amp;gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id=&#34;固定文言--翻訳テーブル&#34;&gt;固定文言 ― 翻訳テーブル&lt;/h2&gt;&#xA;&lt;p&gt;ナビゲーションやボタンのラベルのような、記事に依らない固定の文言は、本文に混ぜません。言語から文字列を引く「翻訳テーブル」でまとめて持ちます。&lt;/p&gt;</description>
			</item>
			<item>
				<title>出力を契約する ― 配ったあとも中身を変えられるようにする</title>
				<link>https://shiro-doro.site/posts/contract-the-output/</link>
				<pubDate>Mon, 15 Jun 2026 11:00:00 +0900</pubDate>
				<guid>https://shiro-doro.site/posts/contract-the-output/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;シリーズ・第 2 回&lt;/strong&gt; — 「個人で静的サイト生成ツールを作って配るまで」（全 5 回予定）。前回は&lt;a href=&#34;https://shiro-doro.site/posts/own-your-writing/&#34;&gt;全体像と前提&lt;/a&gt;を共有しました。各回は単独でも読めます。一覧は &lt;a href=&#34;https://shiro-doro.site/tags/crofty/&#34;&gt;シリーズの記事一覧&lt;/a&gt; から。今回のテーマは「配ったあとも、中身を変えられるようにしておく」です。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;自分だけで使う道具なら、いつでも自由に作り変えられます。問題は、公開して他の人が使い始めたあとです。&lt;/p&gt;&#xA;&lt;p&gt;たとえば設定キーをひとつ改名しただけで、その古いキーを使っていた利用者の環境は動かなくなります。配ったあとは、利用者が頼っている部分を気軽に変えられない。「外から見えるものは、いつか誰かが当てにする」——これは Hyrum の法則として知られる、ごく当たり前の現象です。&lt;/p&gt;&#xA;&lt;p&gt;そこで配る前に決めておきます ― 何を約束として固定し、どこは自由に変えてよいことにするか。crofty はその約束を「出力」に置きました。&lt;/p&gt;&#xA;&lt;h2 id=&#34;入力か出力か&#34;&gt;入力か、出力か&lt;/h2&gt;&#xA;&lt;p&gt;ここでの「入力」と「出力」は、道具（crofty）が&lt;strong&gt;受け取るもの&lt;/strong&gt;（あなたが書く記事や設定）と、&lt;strong&gt;生み出すもの&lt;/strong&gt;（&lt;code&gt;dist&lt;/code&gt;）です。約束をどちらに置くか、二択になります。&lt;/p&gt;&#xA;&lt;table&gt;&#xA;&#x9;&lt;thead&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;入力を契約する&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;th&gt;出力を契約する&lt;/th&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/thead&gt;&#xA;&#x9;&lt;tbody&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;固定するもの&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;設定キー・ファイル構成・テーマ内部&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;生成 HTML に必ず入る項目&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;内部の変更&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;利用者の環境が壊れやすい&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;契約さえ守れば自由&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&#x9;&#x9;&lt;tr&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;つらくなる例&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;キー名の改名、部品の作り替え&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&#x9;&#x9;&lt;td&gt;契約を破ったときだけ&lt;/td&gt;&#xA;&#x9;&#x9;&#x9;&lt;/tr&gt;&#xA;&#x9;&lt;/tbody&gt;&#xA;&lt;/table&gt;&#xA;&lt;p&gt;入力を契約すると内部実装が約束になり、出力を契約すると作り方が自由になります。crofty が選んだのは後者です。&lt;/p&gt;&#xA;&lt;h2 id=&#34;契約の正体--実物を見る&#34;&gt;「契約」の正体 ― 実物を見る&lt;/h2&gt;&#xA;&lt;p&gt;言葉だけだと抽象的なので、実物を見ます。crofty が生成する記事ページの &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; には、必ず次が入っています。&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-html&#34; data-lang=&#34;html&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;c&#34;&gt;&amp;lt;!-- 生成された HTML（抜粋） --&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;&lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;html&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;lang&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;ja&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;記事タイトル · サイト名&lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;title&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;link&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;rel&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;canonical&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;href&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;https://example.com/posts/hello/&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;p&#34;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;meta&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;name&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;viewport&amp;#34;&lt;/span&gt; &lt;span class=&#34;na&#34;&gt;content&lt;/span&gt;&lt;span class=&#34;o&#34;&gt;=&lt;/span&gt;&lt;span class=&#34;s&#34;&gt;&amp;#34;width=device-width, initial-scale=1&amp;#34;&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;    &lt;span class=&#34;c&#34;&gt;&amp;lt;!-- ＋ フィード(/feed.xml)があり、外部へ勝手に通信するタグは無い --&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;  &lt;span class=&#34;p&#34;&gt;&amp;lt;/&lt;/span&gt;&lt;span class=&#34;nt&#34;&gt;head&lt;/span&gt;&lt;span class=&#34;p&#34;&gt;&amp;gt;&lt;/span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;この「必ず入っている項目」こそが契約です。&lt;code&gt;lang&lt;/code&gt; や &lt;code&gt;canonical&lt;/code&gt; が欠けたページを、crofty は出力しません。逆に、ここに&lt;strong&gt;無いもの&lt;/strong&gt;（テーマの作り方、HTML の組み立て方）は、自由に変えてよい部分です。&lt;/p&gt;&#xA;&lt;figure class=&#34;mermaid&#34;&gt;&#xA;&lt;img src=&#34;contract.svg&#34; alt=&#34;crofty の内部実装は自由に変えられ、その下の出力契約（dist の保証）は固定され、その先の公開サイトは契約が同じなら壊れない、という三層の図&#34;&gt;&#xA;&lt;figcaption&gt;固定するのは真ん中の一本（出力契約）だけ。上の実装は動かしてよい&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;h2 id=&#34;文書ではなく機械で守る&#34;&gt;文書ではなく、機械で守る&lt;/h2&gt;&#xA;&lt;p&gt;契約は文章で書くだけだと、いつか実装とずれて腐ります。そこで &lt;code&gt;crofty doctor&lt;/code&gt; が、ビルドした &lt;code&gt;dist&lt;/code&gt; が契約を満たしているか毎回チェックします。&lt;/p&gt;</description>
			</item>
			<item>
				<title>ローカルの Markdown を静的サイトにビルドして公開するツールを作りました</title>
				<link>https://shiro-doro.site/posts/own-your-writing/</link>
				<pubDate>Mon, 15 Jun 2026 10:00:00 +0900</pubDate>
				<guid>https://shiro-doro.site/posts/own-your-writing/</guid>
				<description>&lt;blockquote&gt;&#xA;&lt;p&gt;&lt;strong&gt;シリーズ・第 1 回&lt;/strong&gt; — 「個人で静的サイト生成ツールを作って配るまで」（全 5 回予定）。各回は単独でも読めますが、まとめて辿るなら &lt;a href=&#34;https://shiro-doro.site/tags/crofty/&#34;&gt;シリーズの記事一覧&lt;/a&gt; からどうぞ。今回は全体像と前提の共有で、個々の設計判断は次回以降に取り上げます。&lt;/p&gt;&#xA;&lt;/blockquote&gt;&#xA;&lt;p&gt;書いたものは、どこに残るでしょうか。&lt;/p&gt;&#xA;&lt;p&gt;普段、文章を書く場所の多くは、誰かのプラットフォームの上です。読んでもらいやすい一方で、記事の住所（URL）も、見た目も、読者との接点も、その場所の仕様に委ねることになります。サービスが方針を変えたり、畳んだりすれば、書いたものもそれに従うしかありません。&lt;/p&gt;&#xA;&lt;p&gt;それ自体が悪いわけではないのですが、「長く手元に残しておきたい文章」については、もう少し自分の側に置いておきたい。そう思ったのが、小さな静的サイト生成ツール &lt;a href=&#34;https://crofty.site&#34;&gt;&lt;strong&gt;crofty&lt;/strong&gt;&lt;/a&gt; を作りはじめたきっかけでした。&lt;/p&gt;&#xA;&lt;p&gt;このシリーズでは、その crofty を作って配るまでに考えたこと・つまずいたことを、テーマごとに分けて残していきます。今回はその土台として、「何をしたかったのか」と「全体の流れ」をまとめます。&lt;/p&gt;&#xA;&lt;h2 id=&#34;何をしたかったか&#34;&gt;何をしたかったか&lt;/h2&gt;&#xA;&lt;p&gt;やりたかったことは、ひとことで言うと「自分のコンテンツを所有したまま公開する」ことでした。具体的には、次の 4 つです。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;自分のドメインで出す&lt;/strong&gt; — 記事の住所（URL）を、自分の側に持っておく&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;Markdown で書く&lt;/strong&gt; — プレーンテキストなら、どこにでも移せて、長く読める&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;静的サイトにする&lt;/strong&gt; — データベースを持たず表示が速い。仕組みを入れ替えても記事はそのまま&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;エッジから配信する&lt;/strong&gt; — 世界中どこからでも、軽く開ける&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;平たく言えば、「書いたものが、それを作る道具より長生きする」状態にしておきたかった、ということです。&lt;/p&gt;&#xA;&lt;h2 id=&#34;なぜ既存のツールそのままではないのか&#34;&gt;なぜ既存のツールそのままではないのか&lt;/h2&gt;&#xA;&lt;p&gt;ここで正直な疑問があります。静的サイトを作る道具（いわゆる SSG）はすでに優秀なものが揃っていて、たとえば Hugo はとても速くて安定しています。実際、crofty も内部では Hugo を使っています。では、なぜわざわざ別の道具を作ったのか。&lt;/p&gt;&#xA;&lt;p&gt;理由は、「公開」は書くこと自体だけでは終わらないからです。実際にやってみると、書く前後にこまごました工程がついてきます。&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;記事を書く土台を用意する（最初のセットアップ）&lt;/li&gt;&#xA;&lt;li&gt;体裁が整っているかを確かめる&lt;/li&gt;&#xA;&lt;li&gt;出力が、配信先で正しく開ける形になっているかを確かめる&lt;/li&gt;&#xA;&lt;li&gt;配信する&lt;/li&gt;&#xA;&lt;li&gt;後から見た目や設定を見直す&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;これらを、その都度ばらばらの手順でやるのではなく、&lt;strong&gt;コマンド 1 つずつの薄い手順&lt;/strong&gt;にまとめておきたかった。crofty は、Hugo を置き換えるものではなく、その上に薄くかぶせた小さな道具です。土台は素の Hugo のままなので、いつでも crofty を外して、ただの Hugo プロジェクトとして扱えます。この「いつでも自分の手元に戻せる」感覚も、最初から大事にしていました。&lt;/p&gt;&#xA;&lt;p&gt;なお、「道具を変えても記事は自分のもの」をどう保証するかは、それ自体で 1 本の記事になるテーマなので、次回にゆずります。&lt;/p&gt;&#xA;&lt;h2 id=&#34;全体の流れ&#34;&gt;全体の流れ&lt;/h2&gt;&#xA;&lt;p&gt;実際の流れは、とてもシンプルです。Markdown を書いて、組み立てて、配る。ほぼそれだけです。&lt;/p&gt;&#xA;&lt;figure class=&#34;mermaid&#34;&gt;&#xA;&lt;img src=&#34;pipeline.svg&#34; alt=&#34;Markdown を書き、crofty build で静的サイト（dist）を生成し、crofty deploy でエッジに配信して、読者のブラウザに届くまでの流れ図&#34;&gt;&#xA;&lt;figcaption&gt;書く → 組み立てる → 配る、という一本道&lt;/figcaption&gt;&#xA;&lt;/figure&gt;&#xA;&lt;p&gt;登場するコマンドも、役割で並べるとこれだけです。&lt;/p&gt;</description>
			</item>
	</channel>
</rss>
