<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>CLI on nsys.dev 技術ブログ</title>
		<link>https://blog.nsys.dev/tags/cli/</link>
		<description>Recent content in CLI on nsys.dev 技術ブログ</description>
		<generator>Hugo</generator>
		<language>ja</language>
		
		
		
		
			<lastBuildDate>Mon, 15 Jun 2026 11:00:00 +0900</lastBuildDate>
		
			<atom:link href="https://blog.nsys.dev/tags/cli/index.xml" rel="self" type="application/rss+xml" />
			<item>
				<title>出力を契約する ― 配ったあとも中身を変えられるようにする</title>
				<link>https://blog.nsys.dev/posts/contract-the-output/</link>
				<pubDate>Mon, 15 Jun 2026 11:00:00 +0900</pubDate>
				<guid>https://blog.nsys.dev/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://blog.nsys.dev/posts/own-your-writing/&#34;&gt;全体像と前提&lt;/a&gt;を共有しました。各回は単独でも読めます。一覧は &lt;a href=&#34;https://blog.nsys.dev/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>
	</channel>
</rss>
