開發日誌 #2 - 自訂指令執行功能實作

這次做了什麼?

還記得 Phase 1 做完設定系統後,我就在想:「欸,既然都可以快速切換專案目錄開 CLI 了,那能不能連啟動專案的指令也一起搞定?」

你知道的,開發的時候常常會遇到這種情況:每次要測試前端專案,就得先 cd 到專案目錄,然後打 npm run dev;要跑 Python 後端,又要切過去打 python manage.py runserver。雖然不難,但每天做個十幾二十次,真的會有點煩。

所以這次我就想:「既然我都已經在 Fast CLI Tool 裡管理這些專案路徑了,為什麼不讓它也能記住每個專案要執行的指令呢?」就像是幫每個專案配一個專屬的『啟動鈕』,點下去就開工,多爽!

於是 Custom Command(自訂指令) 功能就這樣誕生了。

現在你可以:

  1. 為每個專案路徑設定一個專屬的指令(像是 npm run devpython manage.py runserverdotnet run
  2. 需要的時候,點一下綠色的 Run 按鈕
  3. CMD 視窗自動開啟,指令開始跑,而且視窗會保持開啟狀態讓你看 log

就這麼簡單!再也不用每次都手動打指令了。

用了哪些技術?

這次的功能其實延續了 Phase 1 建立的 MVVM 架構,所以實作起來還蠻順的。

資料層面:讓 PathItem 記住指令

首先要讓每個路徑項目能夠儲存自訂指令,所以我在 PathItem.cs 這個 Model 裡加了一個新屬性:

  • CustomCommand 屬性:用來存放使用者輸入的指令
  • 因為 PathItem 本身就有實作 INotifyPropertyChanged,所以當你改了指令,系統會自動通知 UI 更新
  • 更棒的是,之前就已經有自動保存機制了,所以指令輸入完,會自動存到 paths.json,完全不用擔心資料遺失

這就是重用架構的好處啊!之前做好的基礎設施,現在加新功能時就能直接享受。

邏輯層面:執行指令的魔法

MainViewModel.cs 裡,我新增了兩個東西:

  1. ExecuteCustomCommandCommand:這是一個 ICommand,綁定到 UI 的 Run 按鈕
  2. ExecuteCustomCommand 方法:真正執行指令的邏輯

執行指令的部分,我用了 cmd.exe /k 這個參數。為什麼是 /k 而不是 /c 呢?因為:

  • /c:執行完指令後,CMD 視窗會自動關閉
  • /k:執行完指令後,CMD 視窗會保持開啟

對開發者來說,保持視窗開啟超重要的!你才能看到伺服器的 log、錯誤訊息、或是 webpack 編譯的進度。想像一下,如果視窗自動關掉,你的 npm run dev 跑起來後馬上消失,那不就完全看不到狀態了嗎?

另外,我還加了一些小細節:

  • 如果指令是空的,Run 按鈕會自動禁用(disabled),避免使用者誤觸
  • 完整的錯誤處理和 Log 記錄,萬一出錯也能追蹤
  • 使用 ProcessStartInfo 來設定工作目錄,確保指令在正確的專案路徑下執行

UI 層面:簡潔直覺的設計

MainWindow.xaml 裡,我把這個功能加在右側的 Path Details 區塊,具體來說是在 Settings 區域內。

介面很簡單:

  • 一個 TextBox 讓你輸入指令(有 placeholder 提示:e.g., npm run dev
  • 一個綠色的 Run 按鈕(指令為空時會自動變灰)
  • 下面還有一行小提示,告訴你 CMD 視窗會保持開啟

為什麼選綠色?因為綠色給人一種「啟動」、「執行」的感覺,就像紅綠燈的綠燈一樣,代表「可以開始了」。而且跟原本的藍色按鈕區分開來,視覺上更清楚。

整個設計的核心理念就是:簡單、直覺、不囉唆。使用者不需要看說明書,光看介面就知道要幹嘛。

遇到什麼挑戰?

說實話,這次開發過程還蠻順利的,主要是因為 Phase 1 打下的基礎架構很扎實。但還是有幾個小地方讓我思考了一下:

挑戰一:要不要讓 CMD 視窗自動關閉?

一開始我在考慮要用 /c 還是 /k。如果用 /c,視窗會自動關閉,介面比較乾淨;但對於需要長時間運行的開發伺服器(像 npm、webpack、Django),你一定會想看到 log。

後來我想,這工具的目標使用者就是開發者啊!開發者肯定更在意能看到執行狀態,而不是介面乾不乾淨。所以最後決定用 /k,讓視窗保持開啟。如果使用者真的想關掉,按個 X 就好了。

挑戰二:按鈕該放在哪裡?

一開始我在考慮要把這個功能放在主視窗的哪個位置。選項有:

  1. 放在路徑清單旁邊(像是每個項目都有個小按鈕)
  2. 放在頂部工具列
  3. 放在右側的 Path Details 區塊

最後選了第三個,因為:

  • Custom Command 是「針對單一路徑」的設定,放在 Path Details 裡語意上最合理
  • 跟其他路徑相關的資訊(Name、Path、Selected CLI)擺在一起,整體性強
  • 不會讓主視窗變得太擁擠

而且這樣設計還有個好處:使用者必須先選擇一個路徑,才能看到和執行對應的指令。這種「選擇 → 查看 → 執行」的流程很自然,不容易出錯。

挑戰三:資料要怎麼持久化?

好在這個問題根本不算問題,因為之前就已經有自動保存機制了!

PathItem 的任何屬性變更都會觸發 PropertyChanged 事件,ViewModel 監聽到後就會自動呼叫 DataService.SavePaths()。所以我加了 CustomCommand 屬性後,完全不用額外寫儲存邏輯,它就會自動存到 paths.json 裡。

這就是良好架構的威力啊!新功能可以無縫整合進來,不會破壞原有的系統。

發佈流程

功能做完後,就要準備發佈了。這次我用的是 .NET 的 Self-Contained 發佈模式。

發佈指令

我下了這個指令:

dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true

這行指令做了幾件事:

  • -c Release:用 Release 配置編譯,會做最佳化
  • -r win-x64:目標平台是 64 位元 Windows
  • --self-contained true:把 .NET Runtime 一起打包進去
  • -p:PublishSingleFile=true:盡量打包成單一檔案

發佈結果

最後產出的檔案在: bin/Release/net9.0-windows/win-x64/publish/

裡面有:

  • fast-cli-tool.exe:主程式(121 MB)
  • 一些必要的 WPF 相關 DLL

雖然檔案有點大(121 MB),但好處是使用者不需要先安裝 .NET Runtime,下載下來直接雙擊就能用。對於不熟悉技術的使用者來說,這是最友善的方式。

而且因為是 Self-Contained,就算使用者電腦上有其他版本的 .NET,也不會互相干擾。我們的程式帶著自己的 Runtime,完全獨立運作。

下一步

這個功能上線後,我覺得 Fast CLI Tool 已經蠻實用的了。但還有一些想法可以繼續做:

短期計畫

  • 多指令支援:也許可以讓一個專案設定多個常用指令(像是 dev、build、test),使用者可以選擇要執行哪一個
  • 指令歷史記錄:記住最近執行過的指令,方便重複使用
  • 更多樣化的顯示模式:除了保持 CMD 視窗開啟,也許可以加個選項讓使用者選擇要不要看到輸出

長期願景

  • Terminal 整合:不只支援 CMD,也支援 PowerShell、Git Bash、Windows Terminal
  • 環境變數設定:讓使用者可以為每個專案設定特定的環境變數
  • 指令範本庫:內建一些常見框架的啟動指令範本(React、Vue、Django、Flask),使用者可以直接套用

不過這些都是後話了。目前這個版本已經能滿足我自己的日常需求,真的大幅提升了開發效率。每次只要點一下就能啟動專案,爽度滿點!


技術總結

這次開發最大的收穫是:好的架構會讓新功能的開發變得超級順暢

Phase 1 建立的 MVVM 架構、自動保存機制、屬性變更通知系統,這次全部都派上用場了。我幾乎沒有改動任何核心邏輯,只是:

  1. 在 Model 加一個屬性
  2. 在 ViewModel 加一個 Command 和方法
  3. 在 View 加一些 UI 元素

就這樣,功能完成了。整個過程不到兩小時,而且完全沒有破壞原有的功能。

這就是為什麼軟體工程師常說「前期多花時間在架構設計上,後期會省下十倍的時間」。真的是這樣啊!

另外,這次也讓我更深刻體會到 User Experience 的重要性。功能再強大,如果使用者不知道怎麼用、或是用起來很麻煩,那也沒意義。所以我花了不少時間在思考:

  • 按鈕該放哪裡?
  • 顏色該用什麼?
  • 要不要加提示文字?
  • CMD 視窗要不要自動關閉?

這些看似小事,但累積起來就決定了工具好不好用。

總之,Phase 2 圓滿完成!現在每次開發都更有效率了,真心覺得這個工具越來越實用。


開發完成於 2025年10月24日

返回 Fast CLI Tool 專案