不縮小範圍
建置所有目前發現的 Lambda。Compute 產生的 all 結果帶有 fallbackReason。
lambda-impact.json 如何由 CLI 產生、怎麼決定 mode,以及為什麼 fallbackReason 不是 constant。
在業務倉庫執行 lets-lambda-build impact,工具即時掃描 Lambda、分析 Git 差異與 Go 依賴,最後把 model.ImpactResult 寫成 JSON。安裝工具、AWS、CSDA 或 build 都不會自動產生這份分析。
# 在業務 repo 執行;已安裝 @v1.0.0,且準備好 Git / Go / vendor set -e lets-lambda-build impact --repo-root . # 預設寫入 ./build/lambda-impact.json lets-lambda-build build --repo-root . \ --impact-file build/lambda-impact.json \ --version "${BITBUCKET_TAG:-${BITBUCKET_COMMIT:-local-test}}"
fallbackReason 是 string,某些分支用固定文字,某些用 fmt.Sprintf(...) 插入檔名、ref 或錯誤。沒有 fallback code enum,也不能當穩定的程式判斷介面。mode 在此版使用三個固定字串,但 Go 欄位本身也是 string;baseStatus 才有命名的 Go 型別與 constants。建置所有目前發現的 Lambda。Compute 產生的 all 結果帶有 fallbackReason。
輸出 lambdas + reasons;省略 fallbackReason。即使清單包含全部,也仍叫 partial。
省略 fallbackReason。通過 build 的基準驗證後,不編譯但仍清除舊 zip。
實線表示呼叫或檔案操作,虛線表示回傳。圖示為成功主路徑;掃描、清檔、讀寫檔等錯誤也會直接終止。impact 與 build 不會在程式內自動串接。
inventory → newInventoryCmd():Discover 後輸出 inventory JSON;不是 impact 的必要前置命令。impact → newImpactCmd():Compute 後寫 impact JSON。build → newBuildCmd() → runBuild():三擇一 --all、--lambdas、--impact-file。本工具沒有 Git hook、排程或自動部署觸發器。README 提供 CI shell 範例;本次查閱的工具倉庫沒有實際業務 pipeline 呼叫。
要確定「你手上的 JSON 是哪個 pipeline step 產生」,仍需該業務 repo 的 pipeline 設定或執行 log。--repo-root 與 --out 也可能改變位置。
build/base_version.txt,不先清除舊 impact JSON。若寫檔前失敗,磁碟上仍可能是上次的結果;務必檢查退出碼。writeJSON() 使用 os.WriteFile,不是 atomic rename。程式沒有持久化的 FSM 物件;以下把 Compute() 的控制流程畫成決策狀態機。每次執行從頭檢查,直到回傳一個最終結果;不會讀取上次的 mode。
基準、工作區、diff、依賴等前置檢查遇到 fallback 就立即回傳。逐檔分類則繼續掃描,但 fallback 只在原本為空時寫入。
已有 fallback,之後選中再多 Lambda 也不會恢復 partial。JSON 只保留第一個原因,不列出全部問題。
FallbackReason string `json:"fallbackReason,omitempty"`
partial / none 中它為空字串,序列化時整個欄位會省略。reasons 是逐 Lambda 的選取原因,與全量的 fallbackReason 不同。
go.mod、go.sum、build-lambda.sh、Makefile、bitbucket-pipelines.yml,以及 tracked vendor/**。go:embed;找到就累加 selected 與 reasons。docs / plans / openapi / localstack / stepfunctions / .devcontainer / fargate / php_fargate / saas / cmd / scripts / sidecar;此規則適用新增、修改、刪除及 rename 兩側。_test.go、Dockerfile*、.dockerignore 只適用還存在的一側;刪除或 rename 舊側若無其他可歸屬 / 可忽略證據,會 fall back。unclassified change... → all。例如根目錄 README.md 不等同 docs/**,也可能觸發全量。注意:目前即使 diff 為空,也會先做依賴與 gitignored 建置輸入檢查,通過後才可能 none。
按程式實際順序排列。下列編號是本頁導讀用,不是工具提供的 reason code。%s / %q / %v 代表執行時插入的變動值。
| 觸發條件 | fallbackReason 文字 / 格式 | 來源位置 |
|---|---|---|
| 01 自動 base 不可用 | ResolveBaseTag() 的 reason;例如 no tag exists anywhere in the repository yet 或 repository is a shallow clone: history is incomplete, cannot be trusted to resolve a base | impact.go:149–163resolvebase.go:77–134 |
| 02 明確 --base 無效 | could not resolve base %q to a commit: %v | impact.go:165–174 |
| 03 工作區查詢失敗 | could not check working tree status: %v | impact.go:187–198 |
| 04 工作區不乾淨 | working tree has uncommitted or untracked changes not covered by .gitignore, so impact cannot trust that HEAD reflects what would actually be built: %s | impact.go:199–208 |
| 05 diff 失敗 | git diff failed: %v | impact.go:210–220 |
| 06 依賴分析失敗 | go list -deps failed: %v | impact.go:222–232 |
| 07 ignored paths 查詢失敗 | could not list gitignored paths: %v | impact.go:238–248 |
| 08 ignored 檔案是建置輸入 | gitignored file ...,分 Go dependency closure、.lambda.csda、Lambda 源碼目錄三類;此檢查豁免 vendor/** 與 build/** | impact.go:249–250impact.go:415–438 |
| 09 強制全量檔案 / tracked vendor | %s changed (%s) | impact.go:293–296 |
| 10 刪除 / rename .lambda.csda | .lambda.csda removed or renamed: %s | impact.go:297–299 |
| 11 無法分類 | unclassified change, cannot prove it is unrelated to any Lambda: %s | impact.go:318 |
firstRelease:完整歷史的本地倉庫沒有 tag,或唯一 tag 正好就在 HEAD。undetermined:shallow clone、Git 查詢失敗、HEAD 有多個 tag、候選基準 commit 有多個 tag、有其他 tag 但 first-parent 歷史找不到可用 tag等。found:HEAD 在 tag 上,找前一個 first-parent tag;不在 tag 上,找 first-parent 歷史最近 tag;解析為 commit SHA 並確認唯一 tag。@v1.0.0 是工具版本,不是業務 repo 的比較基準。fallbackReason 只給人讀。build 不靠解析它決策,而是使用 mode、baseStatus,以及重新驗證的 HEAD / base / tag。
| baseStatus | build 的驗證 | 成功後的 base_version.txt |
|---|---|---|
| found | HEAD 必須一致;base 仍對應唯一且相同的 tag | 寫入基準 tag |
| firstRelease | HEAD 必須一致;重新解析仍須是首次發布 | 不產生 |
| undetermined | 直接報錯,在編譯前停止 | 不產生 |
all + undetermined 並退出 0,但 build 隨後在基準驗證時失敗。即使 all 在選取階段略過 HEAD 檢查,後面的 baseVersionFromImpact() 仍會檢查,因此過期 HEAD 的 all JSON 也不能通過完整 build。下列為原始碼行為的示意資料,不會執行 Go、Git 或建置。SHA 是 placeholder;範例假設 JSON 寫入成功、其他未提及條件均通過。
示意 lambda-impact.json
已有 all / partial / none;之後加入 base 自動解析、HEAD 必須等於 checkout、dirty tree、gitignored 建置輸入、tracked vendor 強制全量與無關根目錄規則。
4579c37:統一基準解析不再用開發分支與 target branch 的 merge-base;改用 first-parent 歷史最近 tag,base 統一記錄 commit SHA。
d68a04e:拆出第二條決策軸新增 baseStatus / baseTag,支援 CSDA 的 base_version.txt。先解析 base,再檢查工作區;即使 scope 降成 all,也保留有效基準。不再從 fallbackReason 文字猜基準是否可信。
impact 的 go list -deps 改為每輪批次執行;三態與主要 fallback 決策分支未再改動。
固定 commit:b1c5774a5f132684a495bb7c8dee35bc2f8ce03a(lambdabuild/v1.0.0)。連結需 Bitbucket 存取權;行號以該版本為準。
兩張圖都已由本機 D2 0.7.1 編譯為 SVG,HTML 內嵌圖檔,不依賴 CDN 或瀏覽器即時 D2 渲染。
d2 --layout elk --pad 32 --no-xml-tag --salt cli cli-flow.d2 cli-flow.svg d2 --layout elk --pad 32 --no-xml-tag --salt state impact-state.d2 impact-state.svg
shape: sequence_diagram
style: {fill: "#ffffff"; stroke: "#cbd5e1"; font-size: 18}
shell: "使用者 / CI shell" {style: {fill: "#e0f2fe"; stroke: "#0284c7"; font-color: "#0c4a6e"}}
cli: "CLI · Cobra / main.go" {style: {fill: "#e0f2fe"; stroke: "#0284c7"; font-color: "#0c4a6e"}}
engine: "Discover / Impact / Build" {style: {fill: "#f1f5f9"; stroke: "#94a3b8"; font-color: "#172b46"}}
disk: "業務 repo / build 目錄" {style: {fill: "#dcfce7"; stroke: "#16a34a"; font-color: "#14532d"}}
impact: "第 1 次 CLI 呼叫:產生分析 JSON" {
shell -> cli: "lets-lambda-build impact\nmain → newRootCmd → newImpactCmd.RunE"
cli -> disk: "1. clearBaseVersionFile()\n清除舊 build/base_version.txt"
cli -> engine: "2. discover.Discover(repoRoot)\n掃描 *.lambda.csda,取得清單"
cli -> engine: "3. impact.Compute(base, head)\nGit 差異 + Go 依賴 + 安全檢查"
engine -> cli: "ImpactResult:all / partial / none\n若回傳 error,停止且不寫新分析 JSON" {style.stroke-dash: 5}
cli -> disk: "4. writeJSON()\n預設 build/lambda-impact.json"
}
build: "第 2 次 CLI 呼叫:讀取 JSON,決定建置範圍" {
shell -> cli: "lets-lambda-build build\n--impact-file build/lambda-impact.json"
cli -> disk: "5. 清除舊 base_version.txt"
cli -> engine: "6. Discover:重新掃描當前 Lambda"
cli -> disk: "7. selectByImpactFile()\nReadFile + Unmarshal → 按 mode 選取"
cli -> cli: "8. baseVersionFromImpact()\n所有 mode 檢查 HEAD / baseStatus / tag\n不可信 → 編譯前報錯停止"
cli -> engine: "9. build.Build(selected)\n清舊 zip → 編譯 / 打包\nnone:不編譯,但仍清舊 zip"
engine -> cli: "建置結果;有失敗就停止" {style.stroke-dash: 5}
cli -> disk: "10. 全部成功才處理 base_version.txt\nfound:寫入 tag;firstRelease:不寫入"
}
direction: down
classes: {
step: {
style: {fill: "#f1f5f9"; stroke: "#94a3b8"; font-color: "#172b46"; border-radius: 12; font-size: 18}
}
check: {
style: {fill: "#e0f2fe"; stroke: "#0284c7"; font-color: "#0c4a6e"; border-radius: 12; font-size: 18}
}
all: {
style: {fill: "#fff1d6"; stroke: "#d97706"; font-color: "#78350f"; border-radius: 12; font-size: 18}
}
error: {
style: {fill: "#ffe4e6"; stroke: "#e11d48"; font-color: "#881337"; border-radius: 12; font-size: 18}
}
good: {
style: {fill: "#dcfce7"; stroke: "#16a34a"; font-color: "#14532d"; border-radius: 12; font-size: 18}
}
}
start: "impact.Compute()\n每次重新分析,不讀取上次結果" {class: check}
head: "① HEAD 可解析,且等於目前 checkout?" {class: check}
base: "② 解析比較基準\n省略 --base:ResolveBaseTag()\n明確 --base:ResolveCommit() + TagAtCommit()" {class: check}
preflight: "③ 依序通過安全檢查\nWorkingTreeStatus → git diff\n→ go list -deps → IgnoredPaths\n→ ignoredBuildInputReason" {class: check}
init: "④ 進入逐檔分類\nselected = {} / reasons = {} / fallback = 空" {class: step}
classify: "每個變更路徑,依優先序分類\n1. 強制全量檔案 / tracked vendor\n2. 被刪除或 rename 舊側的 .lambda.csda\n3. Lambda 目錄 / Go 依賴 / go:embed 歸屬\n4. 無關根目錄 / 安全檔案類型\n5. 無法分類 → fallback\nrename:舊側與新側都分類" {class: step}
latch: "收集結果,處理剩餘變更\n可歸屬 → 累加 selected / reasons\n有 fallback → 只記第一個原因\n已記錄的 fallback 不清除、不覆蓋" {class: step}
finish: "⑤ 全部分類完成\nfallback 是否非空?" {class: check}
selected: "⑥ selected 是否為空?" {class: check}
all: "mode = all\nfallbackReason = 第一個觸發原因\n不輸出 lambdas / reasons\nCompute 回傳 result, nil" {class: all}
none: "mode = none\n不輸出 fallbackReason\n不輸出 lambdas / reasons" {class: good}
partial: "mode = partial\n輸出 lambdas + reasons\n不輸出 fallbackReason\n即使選中全部,也仍是 partial" {class: good}
error: "error(不是 mode)\n非零退出,不寫新結果\n舊 JSON 可能仍留在磁碟" {class: error}
output: "CLI writeJSON()\n寫檔成功 → impact 退出 0\n寫檔失敗 → 非零退出" {class: step}
start -> head
head -> error: "否" {style.stroke: "#e11d48"}
head -> base: "是"
base -> all: "自動:firstRelease / undetermined\n或明確 base 無法解析" {style.stroke: "#d97706"}
base -> preflight: "有可用 commit\n明確 base 無唯一 tag 仍繼續\n但 baseStatus = undetermined"
preflight -> all: "查詢 / 分析失敗、dirty tree\n或 gitignored 建置輸入" {style.stroke: "#d97706"}
preflight -> init: "全部通過"
init -> classify -> latch -> finish
finish -> all: "是(fallback 優先)" {style.stroke: "#d97706"}
finish -> selected: "否"
selected -> none: "是"
selected -> partial: "否"
all -> output
none -> output
partial -> output