<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ja"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://blog.zunda.ninja/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.zunda.ninja/" rel="alternate" type="text/html" hreflang="ja" /><updated>2026-03-23T08:27:30+00:00</updated><id>https://blog.zunda.ninja/feed.xml</id><title type="html">zundaの個人サイト</title><subtitle>ヤングもすなる個人サイトといふものを、おっさんもしてみむとするなり。</subtitle><author><name>zunda</name></author><entry><title type="html">NFCタグを名刺にする</title><link href="https://blog.zunda.ninja/nfc/2026/03/22/nfc-namecard.html" rel="alternate" type="text/html" title="NFCタグを名刺にする" /><published>2026-03-22T10:00:00+00:00</published><updated>2026-03-22T10:00:00+00:00</updated><id>https://blog.zunda.ninja/nfc/2026/03/22/nfc-namecard</id><content type="html" xml:base="https://blog.zunda.ninja/nfc/2026/03/22/nfc-namecard.html"><![CDATA[<p>僕は名札にアイコン、メールアドレス、OpenGPG鍵の指紋、<a href="https://zunda.ninja">ホームページのURL</a>の他、ホームページのURLをQRコードにしたものを掲載しています。ある日、NFCタグも用意すると携帯電話のカメラで撮影しなくてもホームページを訪問してもらえるのではないかと思い立ちました。</p>

<p>ホームページのURLを書き込んだNFCタグは付属の金属リングで名札の紐に取り付けました。下記の写真では左上に写っています。</p>

<p><img src="/assets/2026-03-22-namecard.jpg" alt="" /></p>

<p><a href="https://www.amazon.com/dp/B0FVFCDWK6">Amazonで売られているNFCタグ</a>を購入します。届いたNFCタグをそのまま携帯電話に近づけても、携帯電話は反応しません。</p>

<p>上記のAmazonのページで紹介されていたアプリケーション<a href="https://play.google.com/store/apps/details?id=com.wakdev.wdnfc">NFC Tools</a>を、手元のAndroidの携帯電話(Pixel 9a)にインストールし起動します。Readタブが選択された状態でNFCタグを携帯電話の背面に近づけると、NFCタグとしての情報が得られるようです。</p>

<p><img src="/assets/2026-03-22-read.png" alt="" /></p>

<table>
  <thead>
    <tr>
      <th>項目</th>
      <th>内容</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Tag type</td>
      <td>ISO 14443-3A, NXP - Mifare Ultralight</td>
    </tr>
    <tr>
      <td>Technologies available</td>
      <td>NfcA, Ndef</td>
    </tr>
    <tr>
      <td>ATQA</td>
      <td>0x0044</td>
    </tr>
    <tr>
      <td>SAK</td>
      <td>0x00</td>
    </tr>
    <tr>
      <td>Data format</td>
      <td>NFC Forum Type 2</td>
    </tr>
    <tr>
      <td>Size</td>
      <td>0 / 492 Bytes</td>
    </tr>
    <tr>
      <td>Writable</td>
      <td>Yes</td>
    </tr>
    <tr>
      <td>Can be made Read-Only</td>
      <td>Yes</td>
    </tr>
  </tbody>
</table>

<p>Writeタブを選択し、Add a recordボタンをタップし、URL / URIメニューをタップし、URLを入力します。Writeボタンをタップし、NFCタグを携帯電話の背面に近づけると、指定したURLがNFCタグに書き込まれます。</p>

<p>URLを書き込んだNFCタグをアンロックされた携帯電話の背面に近づけると、ブラウザが開き、URLの内容が表示されます。</p>]]></content><author><name>zunda</name></author><category term="NFC" /><summary type="html"><![CDATA[僕は名札にアイコン、メールアドレス、OpenGPG鍵の指紋、ホームページのURLの他、ホームページのURLをQRコードにしたものを掲載しています。ある日、NFCタグも用意すると携帯電話のカメラで撮影しなくてもホームページを訪問してもらえるのではないかと思い立ちました。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jekyllの記事にMastodonへの共有リンクを表示する</title><link href="https://blog.zunda.ninja/jekyll/mastodon/2026/03/02/share-button.html" rel="alternate" type="text/html" title="Jekyllの記事にMastodonへの共有リンクを表示する" /><published>2026-03-02T20:20:00+00:00</published><updated>2026-03-02T20:20:00+00:00</updated><id>https://blog.zunda.ninja/jekyll/mastodon/2026/03/02/share-button</id><content type="html" xml:base="https://blog.zunda.ninja/jekyll/mastodon/2026/03/02/share-button.html"><![CDATA[<p>ウェブページを<a href="https://blog.joinmastodon.org/2026/03/a-new-share-button/">Mastodonへ共有するボタンがリリースされました</a>。このJekyllサイトの記事にも、下の方にMastodonへ共有するリンクを表示します。</p>

<p><code class="language-plaintext highlighter-rouge">_includes/share.html</code>として下記のような内容のファイルを作ります。</p>

<div class="language-liquid highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{%</span><span class="w"> </span><span class="nt">assign</span><span class="w"> </span><span class="nv">abs_url</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nv">page</span><span class="p">.</span><span class="nv">url</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">absolute_url</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">url_encode</span><span class="w"> </span><span class="cp">%}</span>
&lt;div&gt;
&lt;a href="https://share.joinmastodon.org/#text=<span class="cp">{{</span><span class="w"> </span><span class="nv">page</span><span class="p">.</span><span class="nv">title</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">append</span><span class="p">:</span><span class="w"> </span><span class="s2">" | "</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">append</span><span class="p">:</span><span class="w"> </span><span class="nv">site</span><span class="p">.</span><span class="nv">title</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">url_encode</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">append</span><span class="p">:</span><span class="w"> </span><span class="s2">"%0D"</span><span class="w"> </span><span class="p">|</span><span class="w"> </span><span class="nf">append</span><span class="p">:</span><span class="w"> </span><span class="nv">abs_url</span><span class="w"> </span><span class="cp">}}</span>" target="_blank" rel="noopener" aria-label="Share on Mastodon"&gt;Share on &lt;svg role="img" viewBox="0 0 74 79" fill="currentColor" xmlns="http://www.w3.org/2000/svg" width="1em"&gt;&lt;path d="M73.7014 17.4323C72.5616 9.05152 65.1774 2.4469 56.424 1.1671C54.9472 0.950843 49.3518 0.163818 36.3901 0.163818H36.2933C23.3281 0.163818 20.5465 0.950843 19.0697 1.1671C10.56 2.41145 2.78877 8.34604 0.903306 16.826C-0.00357854 21.0022 -0.100361 25.6322 0.068112 29.8793C0.308275 35.9699 0.354874 42.0498 0.91406 48.1156C1.30064 52.1448 1.97502 56.1419 2.93215 60.0769C4.72441 67.3445 11.9795 73.3925 19.0876 75.86C26.6979 78.4332 34.8821 78.8603 42.724 77.0937C43.5866 76.8952 44.4398 76.6647 45.2833 76.4024C47.1867 75.8033 49.4199 75.1332 51.0616 73.9562C51.0841 73.9397 51.1026 73.9184 51.1156 73.8938C51.1286 73.8693 51.1359 73.8421 51.1368 73.8144V67.9366C51.1364 67.9107 51.1302 67.8852 51.1186 67.862C51.1069 67.8388 51.0902 67.8184 51.0695 67.8025C51.0489 67.7865 51.0249 67.7753 50.9994 67.7696C50.9738 67.764 50.9473 67.7641 50.9218 67.7699C45.8976 68.9569 40.7491 69.5519 35.5836 69.5425C26.694 69.5425 24.3031 65.3699 23.6184 63.6327C23.0681 62.1314 22.7186 60.5654 22.5789 58.9744C22.5775 58.9477 22.5825 58.921 22.5934 58.8965C22.6043 58.8721 22.621 58.8505 22.6419 58.8336C22.6629 58.8167 22.6876 58.8049 22.714 58.7992C22.7404 58.7934 22.7678 58.794 22.794 58.8007C27.7345 59.9796 32.799 60.5746 37.8813 60.5733C39.1036 60.5733 40.3223 60.5733 41.5447 60.5414C46.6562 60.3996 52.0437 60.1408 57.0728 59.1694C57.1983 59.1446 57.3237 59.1233 57.4313 59.0914C65.3638 57.5847 72.9128 52.8555 73.6799 40.8799C73.7086 40.4084 73.7803 35.9415 73.7803 35.4523C73.7839 33.7896 74.3216 23.6576 73.7014 17.4323ZM61.4925 47.3144H53.1514V27.107C53.1514 22.8528 51.3591 20.6832 47.7136 20.6832C43.7061 20.6832 41.6988 23.2499 41.6988 28.3194V39.3803H33.4078V28.3194C33.4078 23.2499 31.3969 20.6832 27.3894 20.6832C23.7654 20.6832 21.9552 22.8528 21.9516 27.107V47.3144H13.6176V26.4937C13.6176 22.2395 14.7157 18.8598 16.9118 16.3545C19.1772 13.8552 22.1488 12.5719 25.8373 12.5719C30.1064 12.5719 33.3325 14.1955 35.4832 17.4394L37.5587 20.8853L39.6377 17.4394C41.7884 14.1955 45.0145 12.5719 49.2765 12.5719C52.9614 12.5719 55.9329 13.8552 58.2055 16.3545C60.4017 18.8574 61.4997 22.2371 61.4997 26.4937L61.4925 47.3144Z" fill="inherit"/&gt;&lt;/svg&gt;&lt;/a&gt;
&lt;/div&gt;
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">minima-2.5.2/_layouts/post.html</code>を<code class="language-plaintext highlighter-rouge">_layouts/post.html</code>としてコピーして、最後に下記の行を追加します。</p>

<div class="language-liquid highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">{%</span><span class="w"> </span><span class="nt">include</span><span class="w"> </span>share.html<span class="w"> </span><span class="cp">%}</span>
</code></pre></div></div>]]></content><author><name>zunda</name></author><category term="jekyll" /><category term="Mastodon" /><summary type="html"><![CDATA[ウェブページをMastodonへ共有するボタンがリリースされました。このJekyllサイトの記事にも、下の方にMastodonへ共有するリンクを表示します。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Vueでテストを書く</title><link href="https://blog.zunda.ninja/vue/test/2026/02/13/vue-test.html" rel="alternate" type="text/html" title="Vueでテストを書く" /><published>2026-02-13T01:45:00+00:00</published><updated>2026-02-25T03:00:00+00:00</updated><id>https://blog.zunda.ninja/vue/test/2026/02/13/vue-test</id><content type="html" xml:base="https://blog.zunda.ninja/vue/test/2026/02/13/vue-test.html"><![CDATA[<p>テストと一緒にVueのアプリを書いてみます。レポジトリを<a href="https://github.com/zunda/clean-url">zunda/clean-url</a>で、稼働例を<a href="https://clean-url.zunda.ninja">clean-url.zunda.ninja</a>で参照できます。</p>

<h2 id="雛形のアプリを作る">雛形のアプリを作る</h2>
<p>新しいディレクトリにVueのアプリを作ります。
手元のXubuntu 24.01には<code class="language-plaintext highlighter-rouge">yarn</code>パッケージとしてyarn 1.22.15が入っています。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">mkdir </span>clean-url
<span class="nv">$ </span><span class="nb">cd </span>clean-url
<span class="nv">$ </span>yarn init
<span class="nv">$ </span>yarn add <span class="nt">-D</span> @vue/cli
<span class="nv">$ </span>yarn run vue create <span class="nb">.</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">package.json</code>を眺めると、<code class="language-plaintext highlighter-rouge">yarn serve</code>で開発用のサーバを起動できそうです。</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"serve"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vue-cli-service serve"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>サーバが起動しました。念のためブラウザで閲覧できるのを確認します。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>yarn serve

<span class="o">(</span>中略<span class="o">)</span>

  App running at:
  - Local:   http://localhost:8080/
  - Network: http://192.168.1.141:8080/

  Note that the development build is not optimized.
  To create a production build, run yarn build.

</code></pre></div></div>

<h2 id="ユニットテストを書いて実行する">ユニットテストを書いて実行する</h2>
<p>今回は、入力されたURLを加工するアプリを書いてみます。</p>

<p>まず、URLをパーズするヘルパ関数を書きます。
<code class="language-plaintext highlighter-rouge">src/helpers.js</code>に、とりあえず最小の機能を持った下記の関数を定義します。</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">export</span> <span class="kd">function</span> <span class="nf">parseUrl</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">return</span> <span class="nx">URL</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Vueによる<a href="https://vuejs.org/guide/scaling-up/testing.html#recommendation">Testing</a>ガイドによると、テストフレームワークには<a href="https://vitest.dev/">Vitest</a>が推奨のようです。インストールします。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>yarn add <span class="nt">-D</span> vitest
</code></pre></div></div>

<p><a href="https://vitest.dev/guide/#writing-tests">Getting Started</a>ガイドに従って、
<code class="language-plaintext highlighter-rouge">package.json</code>に<code class="language-plaintext highlighter-rouge">test</code>スクリプトを追加します。</p>

<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
  </span><span class="nl">"scripts"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nl">"test"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vitest"</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>さらに、<code class="language-plaintext highlighter-rouge">src/helpers.spec.js</code>にユニットテストを書きます。</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">describe</span><span class="p">,</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">test</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vitest</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">parseUrl</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./helpers</span><span class="dl">'</span>

<span class="nf">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">parseUrl</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">parses a minimal URL</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="kd">const</span> <span class="nx">parsed</span> <span class="o">=</span> <span class="nf">parseUrl</span><span class="p">(</span><span class="dl">'</span><span class="s1">https://example.com</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">expected</span> <span class="o">=</span> <span class="p">{</span> <span class="na">host</span><span class="p">:</span> <span class="dl">'</span><span class="s1">example.com</span><span class="dl">'</span><span class="p">,</span> <span class="na">protocol</span><span class="p">:</span> <span class="dl">'</span><span class="s1">https:</span><span class="dl">'</span> <span class="p">}</span>
    <span class="nb">Object</span><span class="p">.</span><span class="nf">entries</span><span class="p">(</span><span class="nx">expected</span><span class="p">).</span><span class="nf">forEach</span><span class="p">(([</span><span class="nx">prop</span><span class="p">,</span> <span class="nx">goal</span><span class="p">])</span> <span class="o">=&gt;</span> <span class="p">{</span>
      <span class="nf">expect</span><span class="p">(</span><span class="nx">parsed</span><span class="p">[</span><span class="nx">prop</span><span class="p">]).</span><span class="nf">toBe</span><span class="p">(</span><span class="nx">goal</span><span class="p">)</span>
    <span class="p">})</span>
  <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p>ユニットテストを実行します。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>yarn <span class="nb">test
</span>yarn run v1.22.15
<span class="nv">$ </span>vitest

 DEV  v4.0.18 /home/zunda/c/src/local/clean-url

 ✓ src/helpers.spec.js <span class="o">(</span>1 <span class="nb">test</span><span class="o">)</span> 3ms
   ✓ parseUrl <span class="o">(</span>1<span class="o">)</span>
     ✓ parses a minimal URL 1ms

 Test Files  1 passed <span class="o">(</span>1<span class="o">)</span>
      Tests  1 passed <span class="o">(</span>1<span class="o">)</span>
   Start at  15:11:03
   Duration  190ms <span class="o">(</span>transform 29ms, setup 0ms, import 44ms, tests 3ms, environment 0ms<span class="o">)</span>

 PASS  Waiting <span class="k">for </span>file changes...
       press h to show <span class="nb">help</span>, press q to quit
</code></pre></div></div>

<p>成功しました。</p>

<h2 id="vueコンポーネントのふるまいをテストする">Vueコンポーネントのふるまいをテストする</h2>
<p>VitestのComponent Testingガイドによると、<a href="https://vitest.dev/guide/browser/component-testing.html#available-testing-library-packages">Vueのテストには<code class="language-plaintext highlighter-rouge">@testing-library/vue</code>を使うのが一般的</a>なようです。<a href="https://testing-library.com/docs/vue-testing-library/intro/#quickstart">Testing LibraryのQuickstart</a>に従ってテストを進めてみます。</p>

<p>ここでは、<code class="language-plaintext highlighter-rouge">src/components/UrlCleaner.vue</code>に定義した、<code class="language-plaintext highlighter-rouge">UrlCleanerComponent</code>をテストしてみます。1つ目の<code class="language-plaintext highlighter-rouge">input</code>エレメントに任意のURLを入力すると、2つ目の読み取り専用の<code class="language-plaintext highlighter-rouge">input</code>エレメントに<code class="language-plaintext highlighter-rouge">v=…</code>以外のクエリパラメータが削除されたURLが出力されるはずです。</p>

<p>この段階では、<code class="language-plaintext highlighter-rouge">src/UrlCleaner.js</code>から<code class="language-plaintext highlighter-rouge">UrlCleaner</code>クラスがエキスポートされて、このクラスには<code class="language-plaintext highlighter-rouge">parse()</code>スタティックメソッドと<code class="language-plaintext highlighter-rouge">removeQueriesExceptFor()</code>メソッドが定義されていました。</p>

<div class="language-vue highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;</span><span class="k">template</span><span class="nt">&gt;</span>
  <span class="nt">&lt;div</span> <span class="na">class=</span><span class="s">"urlCleaner"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;input</span> <span class="na">v-model=</span><span class="s">"dirtyUrl"</span>  <span class="na">placeholder=</span><span class="s">"Dirty URL"</span> <span class="na">type=</span><span class="s">"url"</span> <span class="nt">/&gt;</span>
    <span class="nt">&lt;br/&gt;</span>
    <span class="nt">&lt;input</span> <span class="na">v-model=</span><span class="s">"cleanUrl"</span> <span class="na">placeholder=</span><span class="s">"Clean URL"</span> <span class="na">readonly=</span><span class="s">"readonly"</span> <span class="nt">/&gt;</span>
  <span class="nt">&lt;/div&gt;</span>
<span class="nt">&lt;/</span><span class="k">template</span><span class="nt">&gt;</span>

<span class="nt">&lt;</span><span class="k">script</span><span class="nt">&gt;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">UrlCleaner</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../UrlCleaner</span><span class="dl">'</span>

<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
  <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">UrlCleanerComponent</span><span class="dl">'</span><span class="p">,</span>
  <span class="nf">data</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">return</span> <span class="p">{</span>
      <span class="na">dirtyUrl</span><span class="p">:</span> <span class="dl">""</span>
    <span class="p">}</span>
  <span class="p">},</span>
  <span class="na">computed</span><span class="p">:</span> <span class="p">{</span>
    <span class="nf">cleanUrl</span><span class="p">()</span> <span class="p">{</span>
      <span class="kd">const</span> <span class="nx">x</span> <span class="o">=</span> <span class="nx">UrlCleaner</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">dirtyUrl</span><span class="p">)</span>
      <span class="k">return</span> <span class="nx">x</span> <span class="p">?</span> <span class="nx">x</span><span class="p">.</span><span class="nf">removeQueriesExceptFor</span><span class="p">([</span><span class="dl">"</span><span class="s2">v</span><span class="dl">"</span><span class="p">]).</span><span class="nf">toString</span><span class="p">()</span> <span class="p">:</span> <span class="dl">""</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="p">}</span>
<span class="nt">&lt;/</span><span class="k">script</span><span class="nt">&gt;</span>
</code></pre></div></div>

<p>テストでVueコンポーネントを<code class="language-plaintext highlighter-rouge">import</code>するためには、<code class="language-plaintext highlighter-rouge">@vitejs/plugin-vue</code>をインスールして有効にする必要があるようです<sup id="fnref:plugin-vue"><a href="#fn:plugin-vue" class="footnote" rel="footnote" role="doc-noteref">1</a></sup>。NodeからでブラウザのAPIを利用できるようにするため、jsdomもインストールします<sup id="fnref:jsdom"><a href="#fn:jsdom" class="footnote" rel="footnote" role="doc-noteref">2</a></sup>。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>yarn add <span class="nt">--dev</span> @testing-library/vue @vitejs/plugin-vue jsdom
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">vite.config.js</code>として下記のファイルを作成します。</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">defineConfig</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vite</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">vue</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@vitejs/plugin-vue</span><span class="dl">'</span>

<span class="k">export</span> <span class="k">default</span> <span class="nf">defineConfig</span><span class="p">({</span>
  <span class="na">plugins</span><span class="p">:</span> <span class="p">[</span><span class="nf">vue</span><span class="p">()],</span>
  <span class="na">test</span><span class="p">:</span> <span class="p">{</span>
    <span class="na">include</span><span class="p">:</span> <span class="p">[</span>
      <span class="dl">'</span><span class="s1">./src/**/*.spec.js</span><span class="dl">'</span>
    <span class="p">],</span>
    <span class="na">environment</span><span class="p">:</span> <span class="dl">'</span><span class="s1">jsdom</span><span class="dl">'</span>
  <span class="p">}</span>
<span class="p">})</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">src/tests/UrlCleaner.spec.js</code>にテストを書き始めます。<a href="https://testing-library.com/docs/vue-testing-library/examples/#example-using-v-model">Testing Libraryによる例</a>では、<code class="language-plaintext highlighter-rouge">await fireEvent.update(エレメント, 文字列)</code>で入力するようです。</p>

<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">describe</span><span class="p">,</span> <span class="nx">expect</span><span class="p">,</span> <span class="nx">test</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">vitest</span><span class="dl">'</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">render</span><span class="p">,</span> <span class="nx">cleanup</span><span class="p">,</span> <span class="nx">fireEvent</span><span class="p">,</span> <span class="nx">screen</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">@testing-library/vue</span><span class="dl">'</span>
<span class="k">import</span> <span class="nx">UrlCleanerComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../components/UrlCleaner.vue</span><span class="dl">'</span>

<span class="nf">describe</span><span class="p">(</span><span class="dl">'</span><span class="s1">UrlCleanerComponent</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
  <span class="nf">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">removes queries from a URL</span><span class="dl">'</span><span class="p">,</span> <span class="k">async </span><span class="p">()</span> <span class="o">=&gt;</span> <span class="p">{</span>
    <span class="nf">render</span><span class="p">(</span><span class="nx">UrlCleanerComponent</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">dirty</span> <span class="o">=</span> <span class="nx">screen</span><span class="p">.</span><span class="nf">getByPlaceholderText</span><span class="p">(</span><span class="dl">'</span><span class="s1">Dirty URL</span><span class="dl">'</span><span class="p">)</span>
    <span class="kd">const</span> <span class="nx">clean</span> <span class="o">=</span> <span class="nx">screen</span><span class="p">.</span><span class="nf">getByPlaceholderText</span><span class="p">(</span><span class="dl">'</span><span class="s1">Clean URL</span><span class="dl">'</span><span class="p">)</span>

    <span class="kd">const</span> <span class="nx">dirtyUrl</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">http://example.com/path/?p=parameter&amp;v=keep</span><span class="dl">'</span>
    <span class="kd">const</span> <span class="nx">cleanUrl</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">http://example.com/path/?v=keep</span><span class="dl">'</span>

    <span class="k">await</span> <span class="nx">fireEvent</span><span class="p">.</span><span class="nf">update</span><span class="p">(</span><span class="nx">dirty</span><span class="p">,</span> <span class="nx">dirtyUrl</span><span class="p">)</span>

    <span class="nf">expect</span><span class="p">(</span><span class="nx">dirty</span><span class="p">.</span><span class="nx">value</span><span class="p">).</span><span class="nf">toBe</span><span class="p">(</span><span class="nx">dirtyUrl</span><span class="p">)</span>
    <span class="nf">expect</span><span class="p">(</span><span class="nx">clean</span><span class="p">.</span><span class="nx">value</span><span class="p">).</span><span class="nf">toBe</span><span class="p">(</span><span class="nx">cleanUrl</span><span class="p">)</span>

    <span class="nf">cleanup</span><span class="p">()</span>
  <span class="p">})</span>
<span class="p">})</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">--run</code>オプションを付けて実行すると<code class="language-plaintext highlighter-rouge">vitest</code>は一度だけの実行で停止してくれるようです。テストが通りました。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ yarn test --run src/tests/UrlCleaner.spec.js
yarn run v1.22.15
$ vitest --run src/tests/UrlCleaner.spec.js

 RUN  v4.0.18 /home/zunda/c/src/local/clean-url

 ✓ src/tests/UrlCleaner.spec.js (1 test) 36ms
   ✓ UrlCleanerComponent (1)
     ✓ removes queries from a URL 34ms

 Test Files  1 passed (1)
      Tests  1 passed (1)
   Start at  11:31:41
   Duration  1.13s (transform 95ms, setup 0ms, import 300ms, tests 36ms, environment 631ms)

Done in 1.65s.
</code></pre></div></div>

<h2 id="yarnを新しいものにする">Yarnを新しいものにする</h2>
<p>コードを書き続けていると、
手元のyarn 1.22.15では<code class="language-plaintext highlighter-rouge">node_modules</code>ディレクトリ以下にインストールされるモジュールのバージョンが安定せず、
<code class="language-plaintext highlighter-rouge">yarn install</code>の繰り返しによって時折コマンドが失敗することがわかりました。</p>

<p><a href="https://yarnpkg.com/">Yarn 4+のページ</a>に従って<code class="language-plaintext highlighter-rouge">yarn</code>コマンドを4.12.0に更新したところ、
<code class="language-plaintext highlighter-rouge">yarn test</code>コマンドの実行によってインストールされているモジュールのテストも走るようになりました。
<code class="language-plaintext highlighter-rouge">vite.config.js</code>で定義した<code class="language-plaintext highlighter-rouge">defineConfig</code>関数の引数の<code class="language-plaintext highlighter-rouge">test</code>プロパティに<code class="language-plaintext highlighter-rouge">include</code>プロパティとして<code class="language-plaintext highlighter-rouge">['./src/**/*.spec.js']</code>を追加することで、
<code class="language-plaintext highlighter-rouge">src</code>ディレクトリ以下のテストのみを走らせることができました。</p>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:plugin-vue">
      <p><a href="https://www.issoh.co.jp/tech/details/9465/">Vitest 4登場！パフォーマンス向上やブラウザモード強化など注目の新機能・改良点の全貌とメリットを徹底解説 | 株式会社一創</a> <a href="#fnref:plugin-vue" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:jsdom">
      <p><a href="https://stackoverflow.com/a/70771291/5716182">vue.js - Vue test utils for Vue3 : document is not defined - Stack Overflow</a> <a href="#fnref:jsdom" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>zunda</name></author><category term="vue" /><category term="test" /><summary type="html"><![CDATA[テストと一緒にVueのアプリを書いてみます。レポジトリをzunda/clean-urlで、稼働例をclean-url.zunda.ninjaで参照できます。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jekyllの記事にJavaScriptを書く</title><link href="https://blog.zunda.ninja/jekyll/kramdown/2026/02/04/javascript.html" rel="alternate" type="text/html" title="Jekyllの記事にJavaScriptを書く" /><published>2026-02-04T08:00:00+00:00</published><updated>2026-02-04T08:00:00+00:00</updated><id>https://blog.zunda.ninja/jekyll/kramdown/2026/02/04/javascript</id><content type="html" xml:base="https://blog.zunda.ninja/jekyll/kramdown/2026/02/04/javascript.html"><![CDATA[<p>
<form id="form">
<input type="text" id="src" placeholder="元の文字列" />に
<select id="mark">
<option value="full" selected="">濁点</option>
<option value="half">半濁点</option>
</select>を<button>くっつける</button>と
<input type="text" id="dst" placeholder="こうなる" />
</form>
</p>
<script>
const form = document.getElementById("form");
const mark = document.getElementById("mark");
const src = document.getElementById("src");
const dst = document.getElementById("dst");
function convert(event) {
  event.preventDefault();
  var m = "\u3099";
  if (mark.value == "half") { m = "\u309A"; }
  dst.value = Array.from(src.value).map((c) => c + m).join("");
}
form.addEventListener("submit", convert);
</script>

<p>kramdownにはHTMLだけじゃなくてJavaScriptもそのまま書けるんだ。</p>

<p>例えば、下記のようなフォームとコードを原稿に直書きしておくと、上記のように実行できるようです。Jekyllはページをビルドする時に<a href="https://shopify.github.io/liquid/">Liquidテンプレートエンジン</a>を適用するので、HTMLやJavaScript中にLiquidのタグが現れる場合には<a href="https://shopify.dev/docs/api/liquid/tags/raw">rawタグ</a>で囲むなどの注意が必要かもしれません。</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;p&gt;</span>
<span class="nt">&lt;form</span> <span class="na">id=</span><span class="s">"form"</span><span class="nt">&gt;</span>
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"src"</span> <span class="na">placeholder=</span><span class="s">"元の文字列"</span> <span class="nt">/&gt;</span>に
<span class="nt">&lt;select</span> <span class="na">id=</span><span class="s">"mark"</span><span class="nt">&gt;</span>
<span class="nt">&lt;option</span> <span class="na">value=</span><span class="s">"full"</span> <span class="na">selected</span><span class="nt">&gt;</span>濁点<span class="nt">&lt;/option&gt;</span>
<span class="nt">&lt;option</span> <span class="na">value=</span><span class="s">"half"</span><span class="nt">&gt;</span>半濁点<span class="nt">&lt;/option&gt;</span>
<span class="nt">&lt;/select&gt;</span>を<span class="nt">&lt;button&gt;</span>くっつける<span class="nt">&lt;/button&gt;</span>と
<span class="nt">&lt;input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">id=</span><span class="s">"dst"</span> <span class="na">placeholder=</span><span class="s">"こうなる"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;/form&gt;</span>
<span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;script&gt;</span>
<span class="kd">const</span> <span class="nx">form</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">form</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">mark</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">mark</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">src</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">src</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">dst</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">"</span><span class="s2">dst</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">function</span> <span class="nf">convert</span><span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
  <span class="nx">event</span><span class="p">.</span><span class="nf">preventDefault</span><span class="p">();</span>
  <span class="kd">var</span> <span class="nx">m</span> <span class="o">=</span> <span class="dl">"</span><span class="se">\</span><span class="s2">u3099</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">if </span><span class="p">(</span><span class="nx">mark</span><span class="p">.</span><span class="nx">value</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">half</span><span class="dl">"</span><span class="p">)</span> <span class="p">{</span> <span class="nx">m</span> <span class="o">=</span> <span class="dl">"</span><span class="se">\</span><span class="s2">u309A</span><span class="dl">"</span><span class="p">;</span> <span class="p">}</span>
  <span class="nx">dst</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">src</span><span class="p">.</span><span class="nx">value</span><span class="p">).</span><span class="nf">map</span><span class="p">((</span><span class="nx">c</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="nx">c</span> <span class="o">+</span> <span class="nx">m</span><span class="p">).</span><span class="nf">join</span><span class="p">(</span><span class="dl">""</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">form</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">"</span><span class="s2">submit</span><span class="dl">"</span><span class="p">,</span> <span class="nx">convert</span><span class="p">);</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>]]></content><author><name>zunda</name></author><category term="jekyll" /><category term="kramdown" /><summary type="html"><![CDATA[に 濁点 半濁点 をくっつけると]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jekyllの記事に更新日を記す</title><link href="https://blog.zunda.ninja/jekyll/update/2026/01/30/last-modified-at.html" rel="alternate" type="text/html" title="Jekyllの記事に更新日を記す" /><published>2026-01-30T20:30:00+00:00</published><updated>2026-01-31T00:20:00+00:00</updated><id>https://blog.zunda.ninja/jekyll/update/2026/01/30/last-modified-at</id><content type="html" xml:base="https://blog.zunda.ninja/jekyll/update/2026/01/30/last-modified-at.html"><![CDATA[<p>これまでに公開した記事を改訂したくなったので、記事に更新日を明記します。</p>

<h2 id="last_modified_at変数を表示する">last_modified_at変数を表示する</h2>
<p><a href="https://pl.kpherox.dev/objects/7848aa88-ca45-4f09-833d-62c9ecc13270">jekyll-sitemapやjekyll-feedは<code class="language-plaintext highlighter-rouge">page.last_modified_at</code>に既に対応している</a>とのことです。確かに、jekyll-feed-0.17.0の<code class="language-plaintext highlighter-rouge">feed.xml</code>に下記のような行が含まれていて、<code class="language-plaintext highlighter-rouge">published</code>タグに<code class="language-plaintext highlighter-rouge">date</code>変数が、<code class="language-plaintext highlighter-rouge">updated</code>タグに<code class="language-plaintext highlighter-rouge">last_modified_at</code>変数が適用されることがわかります。</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;title</span> <span class="na">type=</span><span class="s">"html"</span><span class="nt">&gt;</span>{{ post_title }}<span class="nt">&lt;/title&gt;</span>
<span class="nt">&lt;link</span> <span class="na">href=</span><span class="s">"{{ post.url | absolute_url }}"</span> <span class="na">rel=</span><span class="s">"alternate"</span> <span class="na">type=</span><span class="s">"text/html"</span> <span class="na">title=</span><span class="s">"{{ post_title }}"</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;published&gt;</span>{{ post.date | date_to_xmlschema }}<span class="nt">&lt;/published&gt;</span>
<span class="nt">&lt;updated&gt;</span>{{ post.last_modified_at | default: post.date | date_to_xmlschema }}<span class="nt">&lt;/updated&gt;</span>
</code></pre></div></div>

<p>minima-2.5.2の<code class="language-plaintext highlighter-rouge">_layouts/posts.html</code>を<code class="language-plaintext highlighter-rouge">./_layouts/</code>以下にコピーしてきて下記のように編集します。</p>

<div class="language-patch highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/_layouts/post.html b/_layouts/post.html
index abf9696..8abc4a9 100644
</span><span class="gd">--- a/_layouts/post.html
</span><span class="gi">+++ b/_layouts/post.html
</span><span class="p">@@ -6,10 +6,17 @@</span> layout: default
   &lt;header class="post-header"&gt;
     &lt;h1 class="post-title p-name" itemprop="name headline"&gt;{{ page.title | escape }}&lt;/h1&gt;
     &lt;p class="post-meta"&gt;
<span class="gi">+      公開:
</span>       &lt;time class="dt-published" datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished"&gt;
         {%- assign date_format = site.minima.date_format | default: "%b %-d, %Y" -%}
         {{ page.date | date: date_format }}
       &lt;/time&gt;
<span class="gi">+      {%- if page.last_modified_at -%}
+        • 更新:
+        &lt;time class="dt-published" datetime="{{ page.last_modified_at | date_to_xmlschema }}" itemprop="dateModified"&gt;
+          {{ page.last_modified_at | date: date_format }}
+        &lt;/time&gt;
+      {%- endif -%}
</span>       {%- if page.author -%}
         • &lt;span itemprop="author" itemscope itemtype="http://schema.org/Person"&gt;&lt;span class="p-author h-card" itemprop="name"&gt;{{ page.author }}&lt;/span&gt;&lt;/span&gt;
       {%- endif -%}&lt;/p&gt;
</code></pre></div></div>

<p>(追記) Schema.orgを眺めていて<a href="https://schema.org/datePublished"><code class="language-plaintext highlighter-rouge">datePublished</code>プロパティ</a>に加えて<a href="https://schema.org/dateModified"><code class="language-plaintext highlighter-rouge">dateModified</code>プロパティ</a>があるのを見つけました。初出時には<code class="language-plaintext highlighter-rouge">&lt;span&gt;</code>に含めていた更新時刻を<code class="language-plaintext highlighter-rouge">&lt;time itemprop="dateModified"&gt;</code>に含めるよう変更しました。</p>

<h2 id="記事に更新日時を設定する">記事に更新日時を設定する</h2>
<p>フロントマターの<code class="language-plaintext highlighter-rouge">last_modified_at</code>変数を設定する。この記事では下記のように設定してみました。</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">layout</span><span class="pi">:</span> <span class="s">post</span>
<span class="na">title</span><span class="pi">:</span>  <span class="s2">"</span><span class="s">Jekyllの記事に更新日を記す"</span>
<span class="na">date</span><span class="pi">:</span>   <span class="s">2026-01-30 10:30:00 -1000</span>
<span class="na">last_modified_at</span><span class="pi">:</span> <span class="s">2026-01-30 14:20:00 -1000</span>
<span class="na">categories</span><span class="pi">:</span> <span class="s">jekyll update</span>
</code></pre></div></div>]]></content><author><name>zunda</name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[これまでに公開した記事を改訂したくなったので、記事に更新日を明記します。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">bashスクリプトで子プロセスにSIGTERMを転送する</title><link href="https://blog.zunda.ninja/bash/2026/01/30/bash-sigterm.html" rel="alternate" type="text/html" title="bashスクリプトで子プロセスにSIGTERMを転送する" /><published>2026-01-30T02:30:00+00:00</published><updated>2026-01-30T20:45:00+00:00</updated><id>https://blog.zunda.ninja/bash/2026/01/30/bash-sigterm</id><content type="html" xml:base="https://blog.zunda.ninja/bash/2026/01/30/bash-sigterm.html"><![CDATA[<p>HerokuというPlatform as a Service (PaaS)ではコンテナの停止時に<a href="https://devcenter.heroku.com/articles/dyno-shutdown-behavior#sigterm-signal">コンテナ内のすべてのプロセスにSIGTERMを送ります</a>が、ナウでヤングなDockerやk8sなどでは親プロセスのみにSIGTERM送るのが一般的なのだそうです。このような状況でも子プロセスにSIGTERMを転送してくれるbashスクリプトを書きます。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
bundle <span class="nb">exec </span>puma <span class="nt">-C</span> config/puma.rb&amp;
bundle <span class="nb">exec </span>bin/jobs&amp;
<span class="nv">cpids</span><span class="o">=</span><span class="sb">`</span>pgrep <span class="nt">-P</span> <span class="nv">$$</span><span class="sb">`</span><span class="p">;</span>
<span class="nb">trap</span> <span class="s1">'for pid in $cpids; do kill -TERM $pid; done'</span> TERM<span class="p">;</span>
<span class="nb">wait</span> <span class="nt">-n</span><span class="p">;</span>
<span class="nb">kill</span> <span class="nt">-TERM</span> <span class="nv">$$</span><span class="p">;</span>
<span class="nb">wait</span>
</code></pre></div></div>

<p>上記のbashスクリプトは、PumaとSolid Queue(<code class="language-plaintext highlighter-rouge">bin/jobs</code>コマンド)を子プロセスとして起動して、</p>

<ul>
  <li>自スクリプトを実行しているbashプロセスがSIGTERMを受けた、</li>
  <li>自スクリプトを実行しているbashプロセスと子プロセスがSIGTERMを受けた、あるいは、</li>
  <li>いずれかの子プロセスが何らかの理由で停止した</li>
</ul>

<p>時に、</p>

<ol>
  <li>すべての子プロセスにSIGTERMを送って、</li>
  <li>すべての子プロセスが停止したら</li>
  <li>自スクリプトを実行しているbashプロセスを停止する</li>
</ol>

<p>ようになっているはずです。</p>

<h2 id="自スクリプトを実行しているbashプロセスのidを得る">自スクリプトを実行しているbashプロセスのIDを得る</h2>
<p>bashスクリプト内の<code class="language-plaintext highlighter-rouge">$$</code>パラメータは、それを実行しているbashプロセスのIDに展開されます。</p>

<h2 id="子プロセスのidを列挙する">子プロセスのIDを列挙する</h2>
<p>シグナルを転送する先のプロセスIDを得るために、<code class="language-plaintext highlighter-rouge">pgrep</code>コマンドを利用します。<code class="language-plaintext highlighter-rouge">-P</code>オプションを渡すと指定したプロセスの子プロセスを列挙してくれます。<code class="language-plaintext highlighter-rouge">man pgrep</code>より:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-P, --parent ppid,...
       Only match processes whose parent process ID is listed.
</code></pre></div></div>

<p>上記のスクリプトでは、得られたプロセスIDの一覧をシェル変数に格納します。</p>

<h2 id="シグナルを受けた時の動作を指定する">シグナルを受けた時の動作を指定する</h2>
<p><code class="language-plaintext highlighter-rouge">trap</code>組み込みコマンドでスクリプトを実行しているbashプロセスがシグナルを受け取った時の動作を指定できます。文字列として渡した実行内容をシグナルを受け取った時に展開するようです。</p>

<p>上記のスクリプトでは、得られたプロセスIDの一覧を展開します。</p>

<h2 id="子プロセスの終了を待つ">子プロセスの終了を待つ</h2>
<p><code class="language-plaintext highlighter-rouge">wait</code>組み込みコマンドで子プロセスの終了を待つことができます。<code class="language-plaintext highlighter-rouge">-n</code>オプションを付けるといずれかの子プロセスが終了するまでブロックします。オプションを付けないと全ての子プロセスが終了するまでブロックします。</p>

<h2 id="残っている子プロセスにシグナルを送る">残っている子プロセスにシグナルを送る</h2>
<p>上記のスクリプトでは、SIGTERM以外のきっかけでいずれかの子プロセスが停止した場合に、自プロセスにSIGTERMを送ることで<code class="language-plaintext highlighter-rouge">trap</code>に設定したコマンドが実行し残っている子プロセスにもSIGTERMを送ります。</p>]]></content><author><name>zunda</name></author><category term="bash" /><summary type="html"><![CDATA[HerokuというPlatform as a Service (PaaS)ではコンテナの停止時にコンテナ内のすべてのプロセスにSIGTERMを送りますが、ナウでヤングなDockerやk8sなどでは親プロセスのみにSIGTERM送るのが一般的なのだそうです。このような状況でも子プロセスにSIGTERMを転送してくれるbashスクリプトを書きます。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jekyllの記事に脚注を書く</title><link href="https://blog.zunda.ninja/jekyll/update/2026/01/27/footnotes.html" rel="alternate" type="text/html" title="Jekyllの記事に脚注を書く" /><published>2026-01-27T01:40:00+00:00</published><updated>2026-01-27T01:40:00+00:00</updated><id>https://blog.zunda.ninja/jekyll/update/2026/01/27/footnotes</id><content type="html" xml:base="https://blog.zunda.ninja/jekyll/update/2026/01/27/footnotes.html"><![CDATA[<h2 id="kramdownによる記述">kramdownによる記述</h2>

<p>kramdownを利用しているJekyllでは、<a href="https://kramdown.gettalong.org/syntax.html#footnotes">kramdownの仕様</a>に従って脚注を書くことができるようです。</p>

<p>下記のような原稿を書いておくと、</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; 吾輩は猫である。名前はまだ無い。[^footnote]
&gt; 
&gt; [^footnote]: 夏目漱石『吾輩は猫である』より
&gt; 
&gt; どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。
</code></pre></div></div>

<p>下記のように表示されます。</p>

<blockquote>
  <p>吾輩は猫である。名前はまだ無い。<sup id="fnref:footnote"><a href="#fn:footnote" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

  <p>どこで生れたかとんと見当がつかぬ。何でも薄暗いじめじめした所でニャーニャー泣いていた事だけは記憶している。</p>
</blockquote>

<h2 id="スタイルシート">スタイルシート</h2>
<p>minimaテーマには脚注関連のスタイルは定義されていないようです。とりあえず<sup id="fnref:css"><a href="#fn:css" class="footnote" rel="footnote" role="doc-noteref">2</a></sup><code class="language-plaintext highlighter-rouge">minima/_layout.scss</code>に追加しておきます。</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/**
 * footernote
 */</span>
<span class="nc">.footnotes</span> <span class="p">{</span>
  <span class="nl">border-top</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="err">$</span><span class="n">grey-color-light</span><span class="p">;</span>
  <span class="nl">padding</span><span class="p">:</span> <span class="err">$</span><span class="n">spacing-unit</span> <span class="m">0</span><span class="p">;</span>
  <span class="nl">font-size</span><span class="p">:</span> <span class="err">$</span><span class="n">small-font-size</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:footnote">
      <p>夏目漱石『吾輩は猫である』より <a href="#fnref:footnote" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:css">
      <p>zundaはCSSのことをよく知らないので見様見真似です <a href="#fnref:css" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>zunda</name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[kramdownによる記述]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Jekyllのminimaテーマをカスタマイズする</title><link href="https://blog.zunda.ninja/jekyll/update/2026/01/22/cutomize-minima.html" rel="alternate" type="text/html" title="Jekyllのminimaテーマをカスタマイズする" /><published>2026-01-22T06:00:00+00:00</published><updated>2026-01-22T06:00:00+00:00</updated><id>https://blog.zunda.ninja/jekyll/update/2026/01/22/cutomize-minima</id><content type="html" xml:base="https://blog.zunda.ninja/jekyll/update/2026/01/22/cutomize-minima.html"><![CDATA[<p><a href="https://jekyllrb.com/docs/themes/"><code class="language-plaintext highlighter-rouge">jekyll new</code>するとデフォルトで付いてくるMinimaテーマ</a>を自分好みに変更してみます。</p>

<h2 id="フッタの調整">フッタの調整</h2>
<p><code class="language-plaintext highlighter-rouge">_config.yml</code>にサイトの情報として<code class="language-plaintext highlighter-rouge">title</code>のみを設定していると、ビルド後のフッタには</p>

<blockquote>
  <p>zundaの個人サイト</p>

  <p>zundaの個人サイト</p>
</blockquote>

<p>とタイトルが2回繰り返して表示されてしまいました。2回繰り返すほど大事なことではない。</p>

<p>Jekyllのドキュメント<a href="https://jekyllrb.com/docs/includes/">Includes</a>によると、フッタのような要素のテンプレートは<code class="language-plaintext highlighter-rouge">_includes</code>ディレクトリから見つけるか、<a href="https://jekyllrb.com/docs/themes/#understanding-gem-based-themes">gemでインストールされた<code class="language-plaintext highlighter-rouge">_includes</code>ディレクトリから見つける</a>ようです。試しにminima gemの<code class="language-plaintext highlighter-rouge">/_includes/footer.html</code>をこのサイトの<code class="language-plaintext highlighter-rouge">_includes</code>ディレクトリにコピーして編集して閲覧してみたところ、編集内容が反映されました。<code class="language-plaintext highlighter-rouge">site.homepage</code>を設定して<code class="language-plaintext highlighter-rouge">site.author</code>からリンクするように<a href="https://github.com/zunda/zunda.github.io/commit/066ba2ff80540f70a343e435d01f72264f0fa42f">変更</a>してみます。</p>

<h2 id="スタイルシートの調整">スタイルシートの調整</h2>
<p>フォントや色合いも調整してみます。<code class="language-plaintext highlighter-rouge">include</code>タグとは違い<a href="https://talk.jekyllrb.com/t/defining-a-second-font-in-minima/2504">gemとして提供されているディレクトリツリー全部をコピーしてきて編集するのが良</a>さそうです。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cp</span> <span class="nt">-pr</span> vendor/bundle/ruby/4.0.0/gems/minima-2.5.2/_sass <span class="nb">.</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code>で生成したサイトをブラウザで閲覧し、開発者ツールのInspectorで色やフォントが気になる要素をpickしてどのファイルで設定されているのかを調べ、調整します。</p>

<p>ついでに、ビルド時に表示される<code class="language-plaintext highlighter-rouge">Deprecation Warning [color-functions]: lighten() is deprecated.</code>や<code class="language-plaintext highlighter-rouge">darken() is deprecated.</code>といった警告も<a href="https://github.com/jekyll/jekyll/issues/9686#issuecomment-2373992357">jekyll/jekyllのIssueへのコメント</a>を参考に<a href="https://github.com/zunda/zunda.github.io/commit/05940f22ad4c8ebded2e8828bb66a48c380ab911">抑制</a>しておきます。</p>

<div class="language-patch highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/_sass/minima.scss b/_sass/minima.scss
index f772ad5..e253c62 100644
</span><span class="gd">--- a/_sass/minima.scss
</span><span class="gi">+++ b/_sass/minima.scss
</span><span class="p">@@ -1,4 +1,5 @@</span>
 @charset "utf-8";
<span class="gi">+@use "sass:color";
</span> 
 // Define defaults for each variable.
 
<span class="p">@@ -16,8 +17,8 @@</span> $brand-color:      #d88000 !default;
 $brand-color-light:#ffa000 !default;
 
 $grey-color:       #828282 !default;
<span class="gd">-$grey-color-light: lighten($grey-color, 40%) !default;
-$grey-color-dark:  darken($grey-color, 25%) !default;
</span><span class="gi">+$grey-color-light: color.adjust($grey-color, $lightness: 40%, $space: hsl) !default;
+$grey-color-dark:  color.adjust($grey-color, $lightness: -25%, $space: hsl) !default;
</span> 
 $table-text-align: left !default;
 
<span class="gh">diff --git a/_sass/minima/_base.scss b/_sass/minima/_base.scss
index b32254d..b76b843 100644
</span><span class="gd">--- a/_sass/minima/_base.scss
</span><span class="gi">+++ b/_sass/minima/_base.scss
</span><span class="p">@@ -1,3 +1,5 @@</span>
<span class="gi">+@use "sass:color";
+
</span> /**
  * Reset some basic elements
  */
<span class="p">@@ -225,21 +227,21 @@</span> table {
   margin-bottom: $spacing-unit;
   width: 100%;
   text-align: $table-text-align;
<span class="gd">-  color: lighten($text-color, 18%);
</span><span class="gi">+  color: color.adjust($text-color, $lightness: 18%, $space: hsl);
</span>   border-collapse: collapse;
   border: 1px solid $grey-color-light;
   tr {
     &amp;:nth-child(even) {
<span class="gd">-      background-color: lighten($grey-color-light, 6%);
</span><span class="gi">+      background-color: color.adjust($grey-color-light, $lightness: 6%, $space: hsl);
</span>     }
   }
   th, td {
     padding: ($spacing-unit * 0.3333333333) ($spacing-unit * 0.5);
   }
   th {
<span class="gd">-    background-color: lighten($grey-color-light, 3%);
-    border: 1px solid darken($grey-color-light, 4%);
-    border-bottom-color: darken($grey-color-light, 12%);
</span><span class="gi">+    background-color: color.adjust($grey-color-light, $lightness: 3%, $space: hsl);
+    border: 1px solid color.adjust($grey-color-light, $lightness: -4%, $space: hsl);
+    border-bottom-color: color.adjust($grey-color-light, $lightness: -12%, $space: hsl);
</span>   }
   td {
     border: 1px solid $grey-color-light;
</code></pre></div></div>]]></content><author><name>zunda</name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[jekyll newするとデフォルトで付いてくるMinimaテーマを自分好みに変更してみます。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">RSSフィードを利用してJekyllのビルド後に新しいページを検出する</title><link href="https://blog.zunda.ninja/jekyll/update/2026/01/15/find-new-pages-with-rss.html" rel="alternate" type="text/html" title="RSSフィードを利用してJekyllのビルド後に新しいページを検出する" /><published>2026-01-15T22:00:00+00:00</published><updated>2026-01-15T22:00:00+00:00</updated><id>https://blog.zunda.ninja/jekyll/update/2026/01/15/find-new-pages-with-rss</id><content type="html" xml:base="https://blog.zunda.ninja/jekyll/update/2026/01/15/find-new-pages-with-rss.html"><![CDATA[<p>公開RSSを利用してJekyllサイトのビルド後に新しいページを検出してみます。</p>

<h2 id="jekyllの設定">Jekyllの設定</h2>
<p>このサイトの<code class="language-plaintext highlighter-rouge">_config.yml</code>には下記のようにRSSフィードを生成するプラグインが含まれています。</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">plugins</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">jekyll-feed</span>
</code></pre></div></div>

<p>このサイトのカスタムドメインも<code class="language-plaintext highlighter-rouge">_config.yml</code>に設定しておきます。</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">url</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://blog.zunda.ninja"</span>
</code></pre></div></div>

<p>この状態でpush、ビルド、デプロイしてこのサイトのRSSフィードがカスタムドメインを含むURLにリンクするようにしておきます。</p>

<h2 id="検出スクリプトの作成">検出スクリプトの作成</h2>
<p><code class="language-plaintext highlighter-rouge">script/find_new_pages.rb</code>として下記のようなスクリプトを書きます。</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Detect new pages from RSS feeds</span>
<span class="nb">require</span> <span class="s2">"rss"</span>
<span class="nb">require</span> <span class="s2">"uri"</span>

<span class="n">base_url</span> <span class="o">=</span> <span class="s2">"https://blog.zunda.ninja"</span>
<span class="n">latest_rss</span> <span class="o">=</span> <span class="s2">"./_site/feed.xml"</span>
<span class="n">public_rss</span> <span class="o">=</span> <span class="n">base_url</span> <span class="o">+</span> <span class="s2">"/feed.xml"</span>

<span class="k">begin</span>
  <span class="c1"># Compare paths of posts.</span>
  <span class="c1"># ignoring shceme and hostname which maybe different with `jekyll serve`</span>
  <span class="n">new_paths</span> <span class="o">=</span> <span class="p">[</span>
    <span class="no">RSS</span><span class="o">::</span><span class="no">Parser</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="no">File</span><span class="p">.</span><span class="nf">read</span><span class="p">(</span><span class="n">latest_rss</span><span class="p">)),</span> <span class="c1"># current build</span>
    <span class="no">RSS</span><span class="o">::</span><span class="no">Parser</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">public_rss</span><span class="p">)</span>             <span class="c1"># prebious deploy</span>
  <span class="p">].</span><span class="nf">map</span><span class="p">{</span><span class="o">|</span><span class="n">feed</span><span class="o">|</span> <span class="n">feed</span><span class="p">.</span><span class="nf">items</span><span class="p">.</span><span class="nf">map</span><span class="p">{</span><span class="o">|</span><span class="n">item</span><span class="o">|</span> <span class="no">URI</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">item</span><span class="p">.</span><span class="nf">link</span><span class="p">.</span><span class="nf">href</span><span class="p">).</span><span class="nf">path</span><span class="p">}}.</span><span class="nf">inject</span><span class="p">(:</span><span class="o">-</span><span class="p">)</span>

  <span class="k">if</span> <span class="n">new_paths</span><span class="p">.</span><span class="nf">empty?</span>
    <span class="nb">puts</span> <span class="s2">"No new pages."</span>
  <span class="k">else</span>
    <span class="nb">puts</span> <span class="s2">"New pages:</span><span class="se">\n</span><span class="si">#{</span><span class="n">new_paths</span><span class="p">.</span><span class="nf">map</span><span class="p">{</span><span class="o">|</span><span class="n">path</span><span class="o">|</span> <span class="n">base_url</span> <span class="o">+</span> <span class="n">path</span><span class="si">}</span><span class="s2">.join("</span><span class="p">\</span><span class="n">n</span><span class="s2">")}"</span>
  <span class="k">end</span>
<span class="k">rescue</span> <span class="o">=&gt;</span> <span class="n">e</span>
  <span class="c1"># Let build continue even we failed finding new pages</span>
  <span class="nb">puts</span> <span class="s2">"</span><span class="si">#{</span><span class="n">e</span><span class="p">.</span><span class="nf">message</span><span class="si">}</span><span class="se">\n\t</span><span class="s2">from </span><span class="si">#{</span><span class="n">e</span><span class="p">.</span><span class="nf">backtrace</span><span class="p">.</span><span class="nf">last</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
</code></pre></div></div>

<p>このディレクトリが公開されないよう、<code class="language-plaintext highlighter-rouge">_config.yml</code>ファイルに設定を追加しておきます。</p>

<div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">exclude</span><span class="pi">:</span>
  <span class="pi">-</span> <span class="s">script/</span>
</code></pre></div></div>

<h2 id="検出スクリプトのgithub-workflowからの実行">検出スクリプトのGitHub Workflowからの実行</h2>
<p><code class="language-plaintext highlighter-rouge">.github/workflows/jekyll.yml</code>のBuildジョブのBuild with Jekyllステップの後に下記のようなステップを挿入します。</p>

<div class="language-patch highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml
index 615d29b..f93a2e8 100644
</span><span class="gd">--- a/.github/workflows/jekyll.yml
</span><span class="gi">+++ b/.github/workflows/jekyll.yml
</span><span class="p">@@ -47,6 +47,8 @@</span> jobs:
         run: bundle exec jekyll build --baseurl "$"
         env:
           JEKYLL_ENV: production
<span class="gi">+      - name: Find new pages since previous deploy
+        run: ruby ./script/find_new_pages.rb
</span>       - name: Upload artifact
         # Automatically uploads an artifact from the './_site' directory by default
         uses: actions/upload-pages-artifact@v3
</code></pre></div></div>

<p>ここまでの変更点をいったんpushしておきます。</p>

<h2 id="結果">結果</h2>
<p>それではこの記事を公開してみます。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>git add _posts/2026-01-15-find-new-pages-with-rss.md
<span class="nv">$ </span>git commit
<span class="nv">$ </span>git push
</code></pre></div></div>

<p>GitHub ActionsのbuildジョブのFind new pages since previous deployのログは下記のようになりました。やったね!!</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Run ruby ./script/find_new_pages.rb
  ruby ./script/find_new_pages.rb
  shell: /usr/bin/bash -e {0}
  env:
    GITHUB_PAGES: true
New pages:
https://blog.zunda.ninja/jekyll/update/2026/01/15/find-new-pages-with-rss.html
</code></pre></div></div>]]></content><author><name>zunda</name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[公開RSSを利用してJekyllサイトのビルド後に新しいページを検出してみます。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">GitHub ActionsでのJekyllのIncremental Regeneration (現状の観察)</title><link href="https://blog.zunda.ninja/jekyll/update/2026/01/15/jekyll-incremental-regeneration.html" rel="alternate" type="text/html" title="GitHub ActionsでのJekyllのIncremental Regeneration (現状の観察)" /><published>2026-01-15T01:00:00+00:00</published><updated>2026-01-15T01:00:00+00:00</updated><id>https://blog.zunda.ninja/jekyll/update/2026/01/15/jekyll-incremental-regeneration</id><content type="html" xml:base="https://blog.zunda.ninja/jekyll/update/2026/01/15/jekyll-incremental-regeneration.html"><![CDATA[<p><a href="https://jekyllrb.com/docs/configuration/incremental-regeneration/">JekyllのIncremental Regeneration</a>を試してみたいので、GitHub Actionsがビルド結果のファイルをどのように扱うのか確認してみました。</p>

<ul>
  <li>Incremental Regenerationを利用するには、GitHub Actionsによる前回のビルドから、<code class="language-plaintext highlighter-rouge">./_site/</code>以下のアーティファクトと<code class="language-plaintext highlighter-rouge">./.jekyll-metadata</code>ファイルを引き継いで、今回のビルドのために展開する、また、</li>
  <li>新しいポストを検出するためには、どうにかして<code class="language-plaintext highlighter-rouge">jekyll build</code>コマンドに新しいポストのリストを教えてもらうか、<code class="language-plaintext highlighter-rouge">./.jekyll-metadata</code>ファイルなどの変化を検出する</li>
</ul>

<p>必要がありそうです。</p>

<h2 id="ローカルでのビルド">ローカルでのビルド</h2>
<p>Incremental Regenerationでは<code class="language-plaintext highlighter-rouge">.jekyll-metadata</code>ファイルに前回のビルド結果を保存するとのことです。このファイルはトップディレクトリに生成され、メタデータにはフルパスが含まれるようです。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ bundle exec jekyll build -I
$ ruby -rpp -e 'pp Marshal.load(File.read(".jekyll-metadata")).first'
["/home/zunda/c/src/github.com/zunda/zunda.github.io/_posts/2026-01-14-welcome-to-jekyll.md",
 {"mtime" =&gt; 2026-01-14 14:34:52.872091488 -1000,
  "deps" =&gt;
   ["/home/zunda/c/src/github.com/zunda/zunda.github.io/vendor/bundle/ruby/4.0.0/gems/minima-2.5.2/_layouts/post.html",
    "/home/zunda/c/src/github.com/zunda/zunda.github.io/vendor/bundle/ruby/4.0.0/gems/minima-2.5.2/_includes/head.html",
    "/home/zunda/c/src/github.com/zunda/zunda.github.io/vendor/bundle/ruby/4.0.0/gems/minima-2.5.2/_includes/header.html",
    "/home/zunda/c/src/github.com/zunda/zunda.github.io/vendor/bundle/ruby/4.0.0/gems/minima-2.5.2/_includes/footer.html",
    "/home/zunda/c/src/github.com/zunda/zunda.github.io/vendor/bundle/ruby/4.0.0/gems/minima-2.5.2/_includes/social.html",
    "/home/zunda/c/src/github.com/zunda/zunda.github.io/vendor/bundle/ruby/4.0.0/gems/minima-2.5.2/_layouts/default.html"]}]
</code></pre></div></div>

<h2 id="github-actionsによるビルド">GitHub Actionsによるビルド</h2>
<p>GitHub Pagesにこのサイトを公開してもらってGitHub.comのユーザーインターフェースからGitHub ActionsのbuildジョブのBuild with jekyllステップログを確認すると、カレントディレクトリはいつも同じのようです。</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Run bundle exec jekyll build --baseurl ""
Configuration file: /home/runner/work/zunda.github.io/zunda.github.io/_config.yml
            Source: /home/runner/work/zunda.github.io/zunda.github.io
       Destination: /home/runner/work/zunda.github.io/zunda.github.io/_site
</code></pre></div></div>

<h2 id="github-actionsによるアーティファクト">GitHub Actionsによるアーティファクト</h2>
<p>GitHub Pagesにこのサイトを公開してもらってGitHub.comのユーザーインターフェースからGitHub Actionsのログを確認すると、アーティファクトをダウンロードできることに気づきました。Tarファイルをzipしたもので、内容はローカルで<code class="language-plaintext highlighter-rouge">bundle exec jekyll serve</code>した時に<code class="language-plaintext highlighter-rouge">_site/</code>ディレクトリに生成されるものと同様のようです。</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>unzip ~/Downloads/github-pages.zip
<span class="nv">$ </span><span class="nb">tar </span>xf artifact.tar
<span class="nv">$ </span>tree <span class="nb">.</span>
<span class="nb">.</span>
├── 404.html
├── about
│   └── index.html
├── artifact.tar
├── assets
│   ├── main.css
│   ├── main.css.map
│   └── minima-social-icons.svg
├── feed.xml
├── index.html
└── jekyll
    └── update
        └── 2026
            └── 01
                └── 14
                    └── welcome-to-jekyll.html

8 directories, 9 files
</code></pre></div></div>

<p>このアーティファクトは、buildジョブのUpload artifactステップでアップロードされ、deployジョブのDeploy to GitHub Pagesステップでダウンロードされるものと同一のハッシュのようです。</p>]]></content><author><name>zunda</name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[JekyllのIncremental Regenerationを試してみたいので、GitHub Actionsがビルド結果のファイルをどのように扱うのか確認してみました。]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.zunda.ninja/zunda-square.png" /><media:content medium="image" url="https://blog.zunda.ninja/zunda-square.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>