あまり更新しないアプリの v0.2.0。npx 一発で開けるようにした話

1 か月ぶりのバージョンアップ、変更は実質「1 コマンド」です
自作のオフライン Markdown エディタ「offline-md-editor-viewer」の v0.2.0 を出しました。前回の v0.1.1 が 6 月頭だったので、およそ 1 か月ぶり。個人開発のアプリで、正直あまりバージョンアップしません。
今回ユーザーに見える変更は、突き詰めると 1 行で説明できます。
npx offline-md-editor-viewerNode.js が入っている環境なら、これだけでブラウザにエディタが立ち上がるようになりました。npm 配布の追加です。
それだけ?と言われると、それだけです。でもこの 1 行の裏側を掘ると意外と長い話になったので、リリースノートに書ききれなかった部分も含めて残しておきます。
そもそも、このアプリは「置くだけ」で動く
前提を少し。このエディタは HTML ファイル 1 個で完結しています。ライブラリも全部インライン。ブラウザで開けばそのまま動くし、サーバーも通信も要りません。むしろ CSP で外部通信を遮断してあって、オフラインで動くことが売りです。
配布はこれまで GitHub Releases だけでした。ZIP をダウンロードして、展開して、HTML をブラウザで開く。3 手順です。
たった 3 手順。でも「ちょっと試して」と人に勧めるとき、この 3 手順が地味に長い。ダウンロードフォルダのどこに落ちたっけ、から始まる時間があるんですよね。
npm に「アプリ」は置けない。置いたのは「開くだけの係」
最初は単純に「npm に置けば npx で配れるでしょ」と考えていました。ここで一回つまずきます。
npm レジストリは JavaScript のパッケージや CLI ツールの置き場です。ところがこのアプリ、完成品は「ブラウザで開く HTML」と「Windows の exe」。node で実行するものが何もない。つまり npm に本体を置いても、ただのファイル置き場にしかならず、GitHub Releases と役割が被るだけです。
で、発想を変えました。npm に置くのはアプリ本体ではなく、「同梱した HTML を既定のブラウザで開くだけの係」。ランチャーです。

上の図がパッケージの中身です。入っているのは 4 つだけ。本体の HTML(約 1.3MB)、それを開く数十行のスクリプト、README、package.json。依存パッケージはゼロです。
スクリプトがやることも素朴で、OS を見て Windows なら start、Mac なら open、Linux なら xdg-open で HTML を開く。それだけ。アプリ自体はブラウザの中で完結しているので、ランチャーは扉を開けたら仕事が終わりです。
ダウンロードして展開して探して開く、が「1 コマンド打つ」になった。変更としては地味だけど、体験としては別物になりました。
winget も Homebrew も検討して、やめました
npm を足すとき、ついでに他の配布先も検討しました。winget(Windows の公式パッケージマネージャ)、Homebrew、あと bun。
結論から言うと全部見送りです。
・bun は、そもそも独立した配布先ではありませんでした。bunx は npm レジストリから取ってくるので、npm に置けば bun ユーザーにも届きます。1 つ出せば 2 つカバー
・Homebrew は Mac / Linux 向け。このアプリのデスクトップ版は今のところ Windows 限定なので、載せるものがない。Mac 版を作る日が来たら再検討
・winget は載せられます。exe が実在するので。ただ、マニフェストをリリースごとに更新する運用が増えるのと、未署名の exe は winget 経由でも警告が出得るので、コード署名の判断とセットで考えるべき話でした。今回のアプリの規模には見合わないと判断

検討結果を並べたのが上の図です。「配布先は多いほど良い」と思いがちですが、増やした分だけリリースのたびに面倒を見る場所が増える。個人開発で「あまりバージョンアップしない」なら、チャネルは絞ったほうが健全だと思っています。
見えない変更のほうが多かった
CHANGELOG を見返すと、実は npm 対応より修正項目のほうが多い。この 1 か月、AI にコードの監査を何度かやらせていて、そこで確定した問題を潰していました。
ユーザーが気づかない類のものばかりです。たとえば。
・ファイルの新規作成を「原子的」に変更。作成の一瞬の隙に同名ファイルが外から作られると、その中身を黙って消してしまう可能性があった。OS の「無ければ作る、あれば失敗」という仕組みに置き換え
・フォルダツリーの展開で、中に 1 個でも読めないファイル(アクセス権がない等)があると、展開全体が失敗していた。読めないものだけ飛ばすように修正
・どこからも呼ばれていない内部コマンドを 1 つ削除。動作は何も変わらないけど、使っていない入り口は消しておくに越したことはない
「同名ファイルが同時に作られる一瞬の隙」なんて、実際に踏む人はほぼいません。でも踏んだらデータが消える。確率の低さと結果の重さを天秤にかけて、重いほうを先に潰す。地味な 1 か月でした。
リリース作業は、タグを 1 個打つと全部走る
もう 1 つ、今回裏側で組んだのがリリースの自動化です。
npm という配布先が増えたので、「GitHub Releases の ZIP」と「npm のパッケージ」で中身がズレる事故が起きうるようになりました。同じ v0.2.0 なのに中身が違う、というやつです。これが一番怖い。
なので npm 側は「自分でビルドしない」構成にしました。

流れは上の図の通りです。git のタグを 1 個 push すると、CI がビルドして GitHub Release を先に作る。npm 用の処理はその後ろで動いて、公開済みの ZIP をダウンロードし、チェックサムを照合してから HTML を取り出してパッケージに詰める。つまり npm に載る HTML は、Release に載った HTML とバイト単位で同じことが保証されます。
npm への認証もトークンを使っていません。最近の npm には Trusted Publishing という仕組みがあって、GitHub Actions と直接信頼関係を結べます。長生きする秘密情報を持たなくて済むし、パッケージには「どのリポジトリのどのビルドから作られたか」の証明も付く。初めて使いましたが、一発で通りました。ちょっと感動した。
人間がやるのはタグを打つことだけ。あまりバージョンアップしないからこそ、たまにやるリリースで手順を思い出す作業をなくしておきたかった、というのが本音です。
更新頻度が低いのは、悪いことでもない気がしてきた
v0.1.1 から v0.2.0 まで 1 か月。世の中のツールの更新速度からすると、のんびりしています。
ただ、このアプリはオフラインで動く単一 HTML という性質上、一度手元に置いたら壊れない。頻繁な更新を前提にしない作りです。だから 1 回のリリースに、機能追加と、監査で見つけた修正と、配布の仕組みの整備をまとめて詰め込む。そういうリズムでいいのかなと思い始めています。
次のバージョンがいつになるかは分かりません。ただ、次も「npx で最新が落ちてくる」導線はもう出来上がっているので、出すこと自体は今日より楽なはず。
リポジトリはこちらです。Markdown を書く人で、オフラインで完結するエディタが欲しい方はぜひ。
試すのは、Node.js があればこの 1 行で。
npx offline-md-editor-viewer※ ヘッダー画像とインフォグラフィックは AI(画像生成)で作成しています。
書いた人: ishizakahiroshi
群馬の北部で、保護猫2匹と暮らす、在宅エンジニア(何でも屋)
https://github.com/ishizakahiroshi
X(業務委託・各種相談はこちら)
バックエンド・インフラ・AI連携まわりで、業務委託のご相談を受け付けています。フルリモートです。スポットや週2〜3時間からでも歓迎で、いろんな案件に携われたらうれしいです。こんな相談、歓迎です。
