開發日誌 #2 - 自訂指令執行功能實作
這次做了什麼?
還記得 Phase 1 做完設定系統後,我就在想:「欸,既然都可以快速切換專案目錄開 CLI 了,那能不能連啟動專案的指令也一起搞定?」
你知道的,開發的時候常常會遇到這種情況:每次要測試前端專案,就得先 cd 到專案目錄,然後打 npm run dev;要跑 Python 後端,又要切過去打 python manage.py runserver。雖然不難,但每天做個十幾二十次,真的會有點煩。
所以這次我就想:「既然我都已經在 Fast CLI Tool 裡管理這些專案路徑了,為什麼不讓它也能記住每個專案要執行的指令呢?」就像是幫每個專案配一個專屬的『啟動鈕』,點下去就開工,多爽!
於是 Custom Command(自訂指令) 功能就這樣誕生了。
現在你可以:
- 為每個專案路徑設定一個專屬的指令(像是
npm run dev、python manage.py runserver、dotnet run) - 需要的時候,點一下綠色的 Run 按鈕
- CMD 視窗自動開啟,指令開始跑,而且視窗會保持開啟狀態讓你看 log
就這麼簡單!再也不用每次都手動打指令了。
用了哪些技術?
這次的功能其實延續了 Phase 1 建立的 MVVM 架構,所以實作起來還蠻順的。
資料層面:讓 PathItem 記住指令
首先要讓每個路徑項目能夠儲存自訂指令,所以我在 PathItem.cs 這個 Model 裡加了一個新屬性:
CustomCommand屬性:用來存放使用者輸入的指令- 因為 PathItem 本身就有實作
INotifyPropertyChanged,所以當你改了指令,系統會自動通知 UI 更新 - 更棒的是,之前就已經有自動保存機制了,所以指令輸入完,會自動存到
paths.json,完全不用擔心資料遺失
這就是重用架構的好處啊!之前做好的基礎設施,現在加新功能時就能直接享受。
邏輯層面:執行指令的魔法
在 MainViewModel.cs 裡,我新增了兩個東西:
- ExecuteCustomCommandCommand:這是一個 ICommand,綁定到 UI 的 Run 按鈕
- 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 就好了。
挑戰二:按鈕該放在哪裡?
一開始我在考慮要把這個功能放在主視窗的哪個位置。選項有:
- 放在路徑清單旁邊(像是每個項目都有個小按鈕)
- 放在頂部工具列
- 放在右側的 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 架構、自動保存機制、屬性變更通知系統,這次全部都派上用場了。我幾乎沒有改動任何核心邏輯,只是:
- 在 Model 加一個屬性
- 在 ViewModel 加一個 Command 和方法
- 在 View 加一些 UI 元素
就這樣,功能完成了。整個過程不到兩小時,而且完全沒有破壞原有的功能。
這就是為什麼軟體工程師常說「前期多花時間在架構設計上,後期會省下十倍的時間」。真的是這樣啊!
另外,這次也讓我更深刻體會到 User Experience 的重要性。功能再強大,如果使用者不知道怎麼用、或是用起來很麻煩,那也沒意義。所以我花了不少時間在思考:
- 按鈕該放哪裡?
- 顏色該用什麼?
- 要不要加提示文字?
- CMD 視窗要不要自動關閉?
這些看似小事,但累積起來就決定了工具好不好用。
總之,Phase 2 圓滿完成!現在每次開發都更有效率了,真心覺得這個工具越來越實用。
開發完成於 2025年10月24日