Files
arcrun-collector/template_install.go
T
Leo 73a5a67b72 feat(daemon-beta t2): template 代裝——embed v1.18.0 快照(36檔)/冪等不覆寫/daemon 啟動自動鋪/template-install 子命令;scan 加 SkipDirNames 防 system-dev 與 CLAUDE.md 被當知識掃
- 測試 3 支新增全綠(首鋪/冪等護用戶檔/掃描跳 template 產物——第三支先紅抓到 CLAUDE.md 洩入再修綠)
- CLI 實跑:新鋪 36 檔→重跑 0 新檔(冪等證據)
2026-07-24 12:18:37 +08:00

117 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// template_install.go — daemon 代裝 system-dev-templatedaemon-beta task 2)。
//
// leo 四步定稿第 1 步:「幫你在指定資料夾安裝我們現有的 template,這個測過很多次,
// 但要讓 daemon 可用」——即**不走 git clone**template 快照(templatefs/vendored
// 打包進二進位,daemon 直接鋪檔。
//
// 冪等鐵律:**已存在的檔案一律不覆寫**(用戶的 status.md/wiki 是他的資產;
// 升級 template 版本=另案 update 流程,不在本函式)。
package main
import (
"embed"
"fmt"
"io/fs"
"os"
"path/filepath"
)
// templateFS 是 system-dev-template 的 vendored 快照(版本見 templatefs/system-dev/VERSION)。
//
//go:embed all:templatefs
var templateFS embed.FS
const templateFSRoot = "templatefs"
// TemplateInstallResult 是一次代裝的結果帳目。
type TemplateInstallResult struct {
Installed []string `json:"installed"` // 本次新鋪的檔(相對路徑)
Skipped []string `json:"skipped"` // 已存在故跳過的檔
Version string `json:"version"` // 快照版本(templatefs/system-dev/VERSION
}
// TemplateVersion 讀出內嵌快照的版本號。
func TemplateVersion() string {
data, err := templateFS.ReadFile(templateFSRoot + "/system-dev/VERSION")
if err != nil {
return "unknown"
}
v := string(data)
for len(v) > 0 && (v[len(v)-1] == '\n' || v[len(v)-1] == '\r') {
v = v[:len(v)-1]
}
return v
}
// InstallTemplate 把內嵌 template 鋪進 root。冪等:既有檔案跳過不覆寫。
func InstallTemplate(root string) (*TemplateInstallResult, error) {
res := &TemplateInstallResult{Version: TemplateVersion()}
absRoot, err := filepath.Abs(root)
if err != nil {
return nil, err
}
if err := os.MkdirAll(absRoot, 0o755); err != nil {
return nil, fmt.Errorf("建目標資料夾失敗:%w", err)
}
err = fs.WalkDir(templateFS, templateFSRoot, func(p string, d fs.DirEntry, werr error) error {
if werr != nil {
return werr
}
if d.IsDir() {
return nil
}
rel, rerr := filepath.Rel(templateFSRoot, p)
if rerr != nil {
return rerr
}
dest := filepath.Join(absRoot, rel)
if _, serr := os.Stat(dest); serr == nil {
res.Skipped = append(res.Skipped, rel)
return nil // 冪等:不覆寫用戶既有檔
}
if merr := os.MkdirAll(filepath.Dir(dest), 0o755); merr != nil {
return merr
}
data, derr := templateFS.ReadFile(p)
if derr != nil {
return derr
}
if werr2 := os.WriteFile(dest, data, 0o644); werr2 != nil {
return werr2
}
res.Installed = append(res.Installed, rel)
return nil
})
if err != nil {
return nil, err
}
return res, nil
}
// TemplateInstalled 判斷 root 是否已鋪過(以 wiki 標記檔存在為準)。
func TemplateInstalled(root string) bool {
_, err := os.Stat(filepath.Join(root, "system-dev", "wiki", "status.md"))
return err == nil
}
// runTemplateInstall 是 `collector template-install` 子命令主體。
func runTemplateInstall(args []string) int {
fs2 := newFlagSet()
folder := fs2.String("folder", "", "要鋪 template 的資料夾(必填)")
if err := fs2.Parse(args); err != nil {
return 2
}
if *folder == "" {
fmt.Fprintln(os.Stderr, "錯誤:--folder 為必填")
return 2
}
res, err := InstallTemplate(*folder)
if err != nil {
fmt.Fprintln(os.Stderr, "collector template-install:", err)
return 1
}
fmt.Printf("template v%s:新鋪 %d 檔、跳過既有 %d 檔 → %s\n",
res.Version, len(res.Installed), len(res.Skipped), *folder)
return 0
}