設定ファイルの Mac 絶対パスを書き換えずに Windows で動かす:subst とジャンクション

複数台で同じリポジトリを回していると、設定ファイルに絶対パスが入っていて片方の OS でしか動かない、という状況にぶつかります。筆者の環境では、自動化タスクの定義に repoPath: "/Users/hashito/git/web/coffee" のような Mac の絶対パスが入っていました。実体は Windows 機では C:\Users\hasit\git\web\coffee です。

素直な解決は設定ファイルを OS ごとに分岐させることですが、この設定はMac 側が正で、Windows 側の都合で書き換えたくありませんでした。書き換えると両機で差分が出続けて、そのうち片方が壊れます。

そこで使ったのが、ディレクトリジャンクション+subst の仮想ドライブという組み合わせです。結果として、設定ファイルを1文字も変えずに Windows 上の Node.js が /Users/hashito/... を正しく解決するようになりました。

なぜ単純なシンボリックリンクでは足りないのか

Windows で /Users/hashito/git/web/coffee を解決しようとすると、現在のドライブのルートからの絶対パスとして扱われます。つまり cwd が C: 上なら C:\Users\hashito\git\web\coffee を探します。

ここが分かれ道です。C:\Users\ の下には既に実在のユーザープロファイル(この機では hasit)があり、そこに hashito という別名を混ぜるのは避けたい。ユーザープロファイル直下を触ると、権限まわりでも予想外のことが起きます。

したがって必要なのは「\Users\hashito\git\... というパス形だけを再現した、別の場所にあるルート」です。これは次の2段で作れます。

  1. どこか安全な場所に Users\hashito\git\ という階層を作り、その下から実体へジャンクションを張る
  2. その「どこか」を substドライブレターに割り当てる

2 をやる理由は、Node がパスを解決するときの基準がドライブのルートだからです。cwd をその仮想ドライブ上に置けば、/Users/hashito/... は自動的に仮想ドライブのルートから解決されます。

実際の手順

まず受け皿のディレクトリを作り、実体へジャンクションを張ります。mklink /J は cmd の内部コマンドなので、PowerShell からは cmd /c 経由で呼びます。

1
2
3
New-Item -ItemType Directory -Force "$env:USERPROFILE\.fleet-vroot\Users\hashito\git" | Out-Null
cmd /c mklink /J "$env:USERPROFILE\.fleet-vroot\Users\hashito\git\web" "$env:USERPROFILE\git\web"
cmd /c mklink /J "$env:USERPROFILE\.fleet-vroot\Users\hashito\git\scripts" "$env:USERPROFILE\git\scripts"

/J(ディレクトリジャンクション)を使うのがポイントです。/D(ディレクトリシンボリックリンク)だと既定で管理者権限が要りますが、ジャンクションは管理者権限なしで作れます。ローカルのディレクトリを指す用途ならジャンクションで足ります。

次に、この受け皿をドライブレターに割り当てます。

1
subst V: "$env:USERPROFILE\.fleet-vroot"

これで V:\Users\hashito\git\web\coffeeC:\Users\hasit\git\web\coffee の中身を指すようになります。あとはcwd を V ドライブ側に置いてコマンドを実行します。

1
2
Set-Location "V:\Users\hashito\git\scripts\web-fleet"
node automation/run.js whats-due

この状態なら、スクリプト内の /Users/hashito/git/web/coffeeV:\Users\hashito\git\web\coffee として解決され、実体に届きます。

動作確認

解決できているかは、Node に直接聞くのが確実です。

1
2
Set-Location "V:\Users\hashito\git\scripts\web-fleet"
node -e "console.log(require('path').resolve('/Users/hashito/git/web/coffee'))"

V ドライブ上で実行すると V:\Users\hashito\git\web\coffee が返ります。同じコマンドを C: 上で実行すると C:\Users\hashito\git\web\coffee になり、そこには何も無いので、この後のファイル読み込みが ENOENT で落ちます。

この「どのドライブで実行したか」で結果が変わる点が、この構成の最大の落とし穴です。筆者は実際に、C: 上の cwd から同じスクリプトを叩いて

1
data/articles.json がありません(このサイトはこの検査の対象外です)

というエラーを踏みました。ファイルは存在しているのに「無い」と言われる形になるので、原因が分かるまで少し時間がかかります。エラーメッセージに出ているパスの先頭が C:\Users\hashito\(実在しない)になっていないかを最初に見てください。

再起動で消えるのはどちらか

2つの仕掛けは寿命が違います。ここを取り違えると、翌日になって突然動かなくなります。

仕掛け 寿命
mklink /J のジャンクション ファイルシステム上の実体。再起動しても残る
subst の仮想ドライブ ログオンセッション単位。再起動・再ログオンで消える

つまり毎回必要なのは subst だけです。スクリプトの先頭に冪等な1行を置いておけば十分です。

1
if (-not (Test-Path "V:\Users\hashito\git\scripts\web-fleet")) { subst V: "$env:USERPROFILE\.fleet-vroot" }

存在確認を「V ドライブがあるか」ではなく「目的のパスが見えるか」にしているのは、subst は成功しているのにジャンクションのほうが消えている、という壊れ方も拾うためです。

解除したくなったら次のとおりです。

1
subst V: /D

向いている場面・向いていない場面

向いているのは、自分の手元だけの互換レイヤとして使う場合です。設定ファイルは共有リポジトリの正のまま、ローカルの解決だけを合わせられます。

向いていないのは次の場合です。

  • CI など、環境を毎回作り直す場所subst を毎回張る手順が増えるだけで、設定を環境変数化したほうが早い
  • ネットワークドライブを指したい場合。ジャンクションはローカルボリューム向けで、UNC パスを指すなら別の手段が要る
  • チームで共有する手順にする場合。ドライブレターの衝突(誰かが V: を別用途で使っている)が起きる

要するにこれは、設定ファイルを「正しいまま」に保つためにローカル側を歪めるやり方です。歪めた事実は手順書に必ず書き残してください。この構成を知らない人が同じ機械で作業すると、cwd がたまたま C: にあるだけで同じスクリプトが失敗します。


設定ファイルの Mac 絶対パスを書き換えずに Windows で動かす:subst とジャンクション
https://blog.hashito.biz/2026/09/22/windows-subst-junction-mac-absolute-path-node/
著者
hashito
作成日
2026年9月22日
著作権