refactor(products): migrate product command ownership - #101
Merged
Merged
Conversation
Adds hack/snapshot/snapshot.go (Render), snapshot_test.go (TDD unit + baseline golden test), cmd/snapshot_export.go (AddChildrenForSnapshot stub for test-only tree construction without InitConfig/network), and the generated baseline hack/snapshot/testdata/cmdtree.golden (58 KB, full ucloud command tree: path/use/short/flags/defaults/required). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add OutputFormat enum (OutputTable/OutputJSON/OutputYAML) with OutputTable as iota-zero - Add Context struct with in/out/err/format/biz/config fields, NewContext(Deps) constructor, and Out()/In()/Format() accessors - Add Product interface with Metadata() and NewCommand(*Context) contract - Add Metadata struct (Name, Owners, Commands []string, Version) - Relocate base.PickResourceID, base.ParseError, and cmd/ulb.go readFile (as ReadFile) verbatim into pkg/cli/util.go; originals kept in place for existing callers - Add context_test.go (external package cli_test) covering TestPickResourceID and TestOutputFormatDefault Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iceClient reuses same auth path for oauth & AK/SK (no §9 regression) Extract buildCredential/attachHandlers (param-based) shared by NewClient and the parameterless exported wrappers BuildCredential/AttachHandlers (package-var based). NewClient keeps using its own ac/credConfig params so the oauth reactive retry target stays the construction-source profile, not package ConfigIns. Add cli.NewServiceClient[T] reusing base.BuildCredential + base.AttachHandlers so oauth and AK/SK profiles share one credential+handler code path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds pkg/ui as a standalone presentation layer (no base/pkg/cli imports). Printer.PrintList renders to any io.Writer in Table, JSON, or YAML format; Format iota order (Table=0,JSON=1,YAML=2) matches pkg/cli.OutputFormat for lossless numeric conversion in B5. Table rendering is relocated verbatim from base/util.go with fmt.Printf→fmt.Fprintf(out) threading. IsTTY checks *os.File terminal status via go-isatty. Confirm mirrors base.Confirm+ux.Prompt with injected io.Reader/io.Writer streams for testability. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ux/spinner.go: replace fmt.Printf with fmt.Fprintf(s.out, ...) in
Spinner.reset() and Spinner.render(); add NewRefreshTo(out io.Writer)
constructor alongside NewRefresh()
- ux/spinnerv2.go: replace fmt.Printf with fmt.Fprintf(s.out, ...) in
Spin.reset() and Spin.renderToScreen(); renderToString() untouched
- ux/document.go: replace fmt.Printf with fmt.Fprintf(d.out, ...) in
document.reset()
- base/util.go: add pkg/ui import; add non-TTY branch in Spoll() that
drains the done channel and prints a single terminal-state line
("<pollText>...done\n" or "<pollText>...timeout\n") without any
spinner animation when p.Out is not a TTY; Poll() left unchanged
- base/spoll_test.go: two focused tests asserting non-TTY output
contains the done line and no spinner frame runes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…) + bind helpers; wire Context Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…u format) Relocates GenDoukuTree/GenDoukuTreeCustom from the cobra fork's vendor/github.com/spf13/cobra/doc/douku_docs.go into cmd/douku.go as unexported genDoukuTree/genDoukuTreeCustom. Updates cmd/doc_md.go to call the local genDoukuTree instead of doc.GenDoukuTree. Uses only stable upstream cobra/doc API (doc.GenMarkdownCustom), so the build remains green on the fork now and after Task C2 drops the fork for upstream cobra. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d (drop fork pflag completion from cmd) The 9 completion-bearing bind helpers in cmd/util.go (bindRegion, bindRegionS, bindZone, bindZoneEmpty, bindZoneEmptyS, bindZoneS, bindProjectID, bindProjectIDS, bindChargeType) now accept *cobra.Command instead of *pflag.FlagSet. Fork-only SetFlagValuesFunc/SetFlagValues calls are replaced with command.SetCompletion/command.SetFlagValues wrappers. All 241 call sites across 13 cmd/*.go files updated accordingly. The 4 non-completion helpers (bindGroup, bindLimit, bindOffset, bindQuantity) remain unchanged on *pflag.FlagSet. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…etPersistentCompletion) Add SetPersistentCompletion to pkg/command/completion.go that targets cmd.PersistentFlags() instead of cmd.Flags(). The cobra fork's SetFlagValuesFunc silently no-ops when the flag is not in the given FlagSet, so calling SetCompletion (which uses cmd.Flags()) for a flag registered on PersistentFlags() drops tab completion silently. Wire cmd/root.go:61 to use SetPersistentCompletion for --profile, and add TestProfileCompletionRegistered to guard against this regression. Also update stale comment in cmd/udb.go to reference the current API. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The release detector previously used only `svu next` (feat/fix/breaking), so a refactor-only merge produced no release (e.g. PR #4's platform cleanup was skipped). Add a git-log grep for refactor/perf commit types alongside svu next so platform refactors also cut a patch release; pure docs/chore/test/ci/style/ build merges still skip. The bump stays PATCH-ONLY (svu patch) — version policy unchanged. Verified locally: new detector matches all 11 refactor commits since v0.3.4 (=> would release v0.3.5) and correctly ignores chore/docs/ci/test/style; YAML validates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-aware Two fixes from PR #5 review: - BLOCKER (self-review): 'git log | grep -q' under bash -o pipefail (the GitHub Actions default shell) makes git log take SIGPIPE when grep matches early, flipping the whole pipeline non-zero, so the release is WRONGLY SKIPPED — reproduced on the real v0.3.4..HEAD range (printed NO-MATCH despite 11 matching refactor commits). Capture git log to a var and match via here-string (no pipe -> no SIGPIPE). - P2 (Codex): scan %B (full commit message) instead of %s, so a refactor/perf type that only appears in a merge-commit body / PR title is also detected. Verified under bash -eo pipefail: real range -> release; merge-body-only type -> release; chore/docs/ci/test/style -> skip; 5M-line input -> no SIGPIPE. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci(release): also release on refactor/perf commits (keep patch-only)
…nds consistency rule7: no two enabled products may declare the same top-level command name (cobra AddCommand silently shadows duplicates) — 设计 §6.3 一级命令无冲突. rule8: each enabled product's product.go Metadata().Commands must match its products.yaml commands (order-independent, AST-extracted) — 设计 §6.3 命令声明与 products.yaml 一致. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…uct.yaml gen-products/check-product 改为扫描 products/*/product.yaml 聚合;归属真源随产品目录走(product.go Owners 删,归属只在 product.yaml)。gen.go byte-identical、命令树 golden 不变,纯重构。对应 ROADMAP P2a-0。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
新建 hack/owner-gate:判定纯函数 decide —— 改动落在单一 products/X 且 PR author ∈ base 版 owners → 产品自治可 auto-merge;越界(改平台/跨产品/非 owner/增删产品)→ 平台 PR。资格一律用 git show <base>:products/X/product.yaml 判,防 PR 内把自己加进 owners 自证提权。含 11 个单测:8 场景 + name-status 解析 + 临时 git 仓库证明读 base 非 head。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
owner-gate job 跑判定并把裁决以 sticky comment 贴到 PR;auto-merge job 对 自治 PR 跑 gh pr merge --auto --squash(GitHub 原生,fail-closed);hold-gate 在 do-not-merge/hold 标签存在时变红,经分支保护挡住合并。scope-gate 瘦身为仅 conventional 标题,产品/平台/跨产品/owner 路由全交 owner-gate(消除旧 scope-gate 对跨产品 exit 1 硬失败与 spec「跨产品→平台 PR」的冲突)。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…el change 采纳 #6 Codex 评审两条: - owner-gate 原用两点 `git diff BASE HEAD`,分支落后 master 时会冒出无关 master 侧文件、误把自治 PR 降级为平台 PR;改三点 `BASE...HEAD`(merge-base ..head),与 GitHub「Files changed」一致。本地临时仓库实证:两点冒出 phantom D master-only.txt,三点只剩 PR 真改动。 - pull_request 默认不含 labeled/unlabeled,加标签后 hold-gate 不重跑、分支保护 仍见旧绿 check,/hold 急停失效;补 types 让其重跑、经分支保护挡合并。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ci: P2a 准入 gate(owner-gate + 原生 auto-merge)+ rule7/8 + products.yaml 拆分
平台 PR 默认 fail-closed 红,除非作者是 admin 或有 admin(≠作者)在当前 head commit 上的 Approve;产品 owner 自治不变。修跨产品 rename 误路由 + rename-away 下线绕过。
P3 Part 0 — 平台地基(C2 ux 平台暴露)。纯平台改动,产品命令零改动;为 batch-1 产品迁移补齐平台契约。
- pkg/cli/result.go: OpResultRow / ProgressWriter / EmitResult(结构化写出,table no-op、json/yaml 出 stdout)
- pkg/cli/progress.go: 并发进度 facade(NewProgress/NewBlock/Refresh/Disable/Sspoll)+ ConcurrentAction(Block=ux.Block)
- base/util.go: Poller.Sspoll 非 TTY 抑制(镜像 Spoll)
- ux/document.go: 导出 NewDocument(writer) + 非 TTY 自动抑制(全局 Doc 不动)
- pkg/cli/forward.go, pkg/command/bind.go: BindGroup / RegionList·ZoneList·ProjectList getter / AllRegions(保 error) / Log*·LogFilePath forwarder
- base/{client,log}.go: 平台请求日志 handler(logToFile 只写 cli.log、不传 DAS)替代产品手拼
验证:build/vet/golden/check-product/test/6平台 全绿;真实 CLI 增删改查 + EIP allocate→release 计费生命周期对比 master 行为零回归。
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…rewall/udisk/eip/image/uhost) Migrate the six compute-domain products out of the monolithic cmd/ package into self-contained products/ packages on the pkg/cli platform facade. - Migrated: uphost, firewall, udisk, eip, image, uhost (behind ctx *cli.Context; no base.BizClient/ux singletons; cross-product compat shims kept for batch-2). - Bug fixes surfaced during migration: uphost --uphost-id filter wiring, firewall copy --src-fw enforcement, udisk --snapshot-id (rename + binding). - Machine-output contract (aws/gcloud model): stdout carries only the structured result (EmitResult); narration/progress/confirm prompts and errors/warnings go to stderr; failures exit non-zero without dumping usage. uhost list --output is the AWS-style format selector (legacy wide flag removed). - OAuth: keep base.AuthCredential pointer stable so registration-time product clients see token refreshes. Verified end-to-end against the live API and diffed against upstream master; build/vet/golden/check-product/test-suite green. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…example (P3.5) (#10) * refactor(products): own domain constants per product, drop model/status+model/cli imports * feat(check-product): rule-9 enforces the §6.1 product import whitelist * feat(snapshot): per-product goldens under products/<name>/testdata; platform golden prunes product subtrees Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(example): per-verb file layout for product_onboarding (spec §2 alignment) * chore(hack): fix stale products.yaml references in doc comments (per-product product.yaml since P2a-0) * feat(example): demonstrate the machine-output contract (EmitResult/ProgressWriter) in write verbs --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ressWriter, drop local output helpers) (#11)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
… (P3.6 B7) (#19) * feat(check-product): rule-10 bans grab-bag filenames and multi-constructor files Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(ci): grant auto-merge job contents:write (enablePullRequestAutoMerge requirement) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…ts (#20) * fix(cli): EmitResult emits [] not null for empty result set (F-6) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): non-zero exit when a product command reports an error, excluding completion (F-3 core) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(cli): robust completion detection in exit gate; comment/test cleanup (F-3 core review) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: route terminal command errors through ctx.HandleError for non-zero exit (F-3 Tier2) * feat(cli): Confirm returns (bool,error) for non-interactive confirmation (F-5 platform) * fix: handle Confirm (bool,error) at simple call sites (F-5 category A) * fix: handle Confirm (bool,error) in batch loops, partial-success semantics (F-5 category B) * fix: propagate Confirm three-state through uhost helper chains (F-5 category C) * fix(example): update onboarding delete confirmation for Confirm error return * fix(udisk): keep legacy snaphost-id snapshot flag alias * Revert "fix(udisk): keep legacy snaphost-id snapshot flag alias" This reverts commit ecabefd. * chore: rerun PR gate after title fix * fix(cli): address confirmation review edge cases * fix(mysql): confirm restart before config apply --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Validation
go test ./... -skip TestUhostgo vet ./...go test ./hack/snapshot/...go run ./hack/check-productgo test ./cmd ./pkg/command ./hack/snapshot -skip TestUhostucloud completion bash|zsh|fish|powershellgenerated shell completion scripts successfullyucloud __complete ""anducloud __complete uhost ""returned Cobra dynamic-completion candidates successfullycompletion.--profile ucloud: 19/19 semantic JSON cases passed across migrated products (uhost,uphost,udisk,eip,firewall,image,mysql db) and legacy/platform commands (project,region,subnet,vpc,bw pkg,bw shared,udpn,ulb,gssh,pathx,redis,memcache).--profile ucloud:firewallcreate/update/add-rule/delete,eipallocate/modify-bw/release,udiskcreate/expand/delete,uhostcreate/stop/start/restart/delete,imagecreate-image/delete,mysqlcreate/stop/start/restart/deletevpccreate/delete,subnetcreate/delete,ulbinner create/deletecodex-e2etemporary resources remaining.Notes