From f484ddd0782609460d7c1e10e22b2e75533b0794 Mon Sep 17 00:00:00 2001 From: Cheese Date: Mon, 20 Jul 2026 23:59:38 +0800 Subject: [PATCH 1/4] feat: simplify filesystem deletion workflow --- .github/workflows/live-e2e.yml | 3 +- .gitmodules | 4 -- AGENTS.md | 22 +++++----- README.md | 1 - docs/pingcap-docs/docs-cn | 1 - docs/present.md | 3 +- docs/spec/done/0009-tdc-fs-control-plane.md | 8 ++-- docs/spec/done/0013-github-actions-ci-cd.md | 2 +- .../done/0016-profile-fs-resource-registry.md | 2 +- .../done/0019-pingcap-tdc-documentation.md | 43 +++++++------------ e2e/cli_test.go | 11 ++++- e2e/live_test.go | 14 +++--- internal/cli/commands.go | 27 +++++------- internal/fs/control.go | 14 ++---- internal/fs/drive9_companion_test.go | 10 ++--- 15 files changed, 68 insertions(+), 97 deletions(-) delete mode 160000 docs/pingcap-docs/docs-cn diff --git a/.github/workflows/live-e2e.yml b/.github/workflows/live-e2e.yml index bf63105..0f08be5 100644 --- a/.github/workflows/live-e2e.yml +++ b/.github/workflows/live-e2e.yml @@ -107,5 +107,4 @@ jobs: fi bin/tdc fs delete-file-system \ --profile live-e2e \ - --file-system-name "$TDC_LIVE_FS_NAME" \ - --confirm-file-system-name "$TDC_LIVE_FS_NAME" + --file-system-name "$TDC_LIVE_FS_NAME" diff --git a/.gitmodules b/.gitmodules index a68c1ff..82eee0e 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,7 +11,3 @@ path = docs/pingcap-docs/docs url = git@github.com:pingcap/docs.git branch = release-8.5 -[submodule "docs/pingcap-docs/docs-cn"] - path = docs/pingcap-docs/docs-cn - url = git@github.com:pingcap/docs-cn.git - branch = release-8.5 diff --git a/AGENTS.md b/AGENTS.md index af82138..5955b17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,7 @@ Implemented: `docs/spec/done/0018-fs-token-auth-and-config-free-access.md` - install and update distribution from `docs/spec/done/0012-install-and-update-distribution.md` -- English and Chinese PingCAP Preview documentation from +- English PingCAP Preview documentation from `docs/spec/done/0019-pingcap-tdc-documentation.md` - `tdc configure` - `tdc update --check` @@ -288,7 +288,6 @@ docs/priciples.md product principles and MVP scope source of truth docs/spec/ pending requirement specs docs/spec/done/ completed requirement specs docs/pingcap-docs/docs/ pingcap/docs English documentation submodule -docs/pingcap-docs/docs-cn/ pingcap/docs-cn Chinese documentation submodule ref/ read-only reference implementations ``` @@ -344,6 +343,9 @@ Follow these rules unless `docs/priciples.md` is updated: credential source. - Every command should be usable by scripts and agents without terminal-specific assumptions. +- When creating a pull request, check whether the target repository has a pull + request template. If it does, follow that template when writing the pull + request description. - Help must work as: - `tdc help` - `tdc help` @@ -427,8 +429,8 @@ Implemented command behavior: - `tdc fs create-file-system --file-system-name workspace --wait` - `tdc fs create-file-system --file-system-name workspace --dry-run` - `tdc fs create-file-system --file-system-name scratch --set-default` -- `tdc fs delete-file-system --file-system-name workspace --confirm-file-system-name workspace` -- `tdc fs delete-file-system --file-system-name workspace --confirm-file-system-name workspace --dry-run` +- `tdc fs delete-file-system --file-system-name workspace` +- `tdc fs delete-file-system --file-system-name workspace --dry-run` - `tdc fs list-file-systems` - `tdc fs describe-file-system --file-system-name workspace` - `tdc fs set-default-file-system --file-system-name workspace` @@ -595,12 +597,12 @@ Registered command surface: Do not rename commands without updating specs, README, e2e tests, and AGENTS. Any code change that changes user-visible behavior must keep README.md in sync. -It must also update the matching English and Chinese tdc pages under -`docs/pingcap-docs/` when the published behavior changes. Keep both locale -trees structurally identical, use globally unique `tdc-` basenames, preserve -the standard Preview note on every page, and update both `TOC-ai.md` files and -both `ai/_index.md` pages when adding or removing a document. Validate command -names and flags against the compiled CLI help, not historical specs or demos. +It must also update the matching English tdc pages under +`docs/pingcap-docs/docs/` when the published behavior changes. Use globally +unique `tdc-` basenames, preserve the standard Preview note on every page, and +update `TOC-ai.md` and `ai/_index.md` when adding or removing a document. +Validate command names and flags against the compiled CLI help, not historical +specs or demos. ## Configuration And Credentials diff --git a/README.md b/README.md index bd2ae22..c75c170 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,6 @@ tdc update --target-version v0.1.1 ## Documentation - [English Preview documentation](docs/pingcap-docs/docs/ai/tdc/tdc-overview.md) -- [中文预览文档](docs/pingcap-docs/docs-cn/ai/tdc/tdc-overview.md) ## Build from source diff --git a/docs/pingcap-docs/docs-cn b/docs/pingcap-docs/docs-cn deleted file mode 160000 index cd88bf5..0000000 --- a/docs/pingcap-docs/docs-cn +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cd88bf5a7b24b4d6752346b37a40dba12747d5f7 diff --git a/docs/present.md b/docs/present.md index 8e9453a..31308f7 100644 --- a/docs/present.md +++ b/docs/present.md @@ -387,8 +387,7 @@ rm -f /tmp/tdc-demo-token.txt ```bash bin/tdc fs delete-file-system \ - --file-system-name tdc-demo-workspace \ - --confirm-file-system-name tdc-demo-workspace + --file-system-name tdc-demo-workspace ``` 删除演示 cluster: diff --git a/docs/spec/done/0009-tdc-fs-control-plane.md b/docs/spec/done/0009-tdc-fs-control-plane.md index c1ad284..4129c45 100644 --- a/docs/spec/done/0009-tdc-fs-control-plane.md +++ b/docs/spec/done/0009-tdc-fs-control-plane.md @@ -65,7 +65,7 @@ canonical region: tdc fs check-file-system tdc fs create-file-system --file-system-name workspace --dry-run tdc fs create-file-system --file-system-name workspace -tdc fs delete-file-system --file-system-name workspace --confirm-file-system-name workspace +tdc fs delete-file-system --file-system-name workspace ``` The user chooses one canonical region code during `tdc configure`; the CLI @@ -85,8 +85,8 @@ resolves all fs endpoints internally. selecting `tidb_cloud_native` entries. - `internal/fs/status` defines the structured check response with local config, credential, permission, endpoint, and service health entries. -- Delete safety is implemented through an explicit long flag, initially - `--confirm-file-system-name `. +- Deletion requires an explicit `--file-system-name ` and does not add a + console-style name confirmation flag. ## API Call Chain @@ -140,7 +140,7 @@ Command mapping: canonical `fs_region_code` in `[profile]` of `~/.tdc/config`. 5. Store `fs_api_key` in `[profile]` of `~/.tdc/credentials`. - `tdc fs delete-file-system` - 1. Validate `--confirm-file-system-name`. + 1. Validate `--file-system-name`. 2. Resolve the tdc fs base URL through the region manifest. 3. Load the stored resource API key and call `DELETE /v1/tenant` with `Authorization: Bearer `. diff --git a/docs/spec/done/0013-github-actions-ci-cd.md b/docs/spec/done/0013-github-actions-ci-cd.md index a582947..f3b8e17 100644 --- a/docs/spec/done/0013-github-actions-ci-cd.md +++ b/docs/spec/done/0013-github-actions-ci-cd.md @@ -81,7 +81,7 @@ Then it runs: FS_NAME="tdc-live-e2e-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" bin/tdc fs create-file-system --profile live-e2e --file-system-name "$FS_NAME" make live-e2e -bin/tdc fs delete-file-system --profile live-e2e --file-system-name "$FS_NAME" --confirm-file-system-name "$FS_NAME" +bin/tdc fs delete-file-system --profile live-e2e --file-system-name "$FS_NAME" ``` The workflow must not commit or upload `~/.tdc/` as an artifact. diff --git a/docs/spec/done/0016-profile-fs-resource-registry.md b/docs/spec/done/0016-profile-fs-resource-registry.md index bb62d1c..1eafe39 100644 --- a/docs/spec/done/0016-profile-fs-resource-registry.md +++ b/docs/spec/done/0016-profile-fs-resource-registry.md @@ -101,7 +101,7 @@ New or changed commands: ```bash tdc fs create-file-system --file-system-name [--set-default] [--wait] [--dry-run] -tdc fs delete-file-system --file-system-name --confirm-file-system-name [--dry-run] +tdc fs delete-file-system --file-system-name [--dry-run] tdc fs list-file-systems tdc fs describe-file-system --file-system-name tdc fs check-file-system [--file-system-name ] diff --git a/docs/spec/done/0019-pingcap-tdc-documentation.md b/docs/spec/done/0019-pingcap-tdc-documentation.md index 22c4a95..2797799 100644 --- a/docs/spec/done/0019-pingcap-tdc-documentation.md +++ b/docs/spec/done/0019-pingcap-tdc-documentation.md @@ -2,9 +2,9 @@ ## Goal -Publish complete Preview documentation for tdc in the English and Chinese PingCAP documentation repositories. The documentation must help a new user reach a successful result quickly, cover every implemented command, explain the tdc and Drive9 ownership boundary, and provide scenario-based examples for database, filesystem, Git workspace, journal, vault, and ephemeral agent workflows. +Publish complete Preview documentation for tdc in the English PingCAP Cloud documentation repository. The documentation must help a new user reach a successful result quickly, cover every implemented command, explain the tdc and Drive9 ownership boundary, and provide scenario-based examples for database, filesystem, Git workspace, journal, vault, and ephemeral agent workflows. -The English documentation lives in the `pingcap/docs` submodule at `docs/pingcap-docs/docs/`. The Chinese translation lives in the `pingcap/docs-cn` submodule at `docs/pingcap-docs/docs-cn/`. Both submodules track the `release-8.5` branch and must keep matching document structures and links. +The documentation lives in the `pingcap/docs` submodule at `docs/pingcap-docs/docs/`. The submodule tracks the `release-8.5` branch. TiDB Cloud documentation is not translated into `pingcap/docs-cn`. This is a documentation-only requirement. It does not add or change tdc runtime behavior, TiDB Cloud APIs, Drive9 APIs, configuration formats, credentials, commands, or dependencies. @@ -22,15 +22,7 @@ English: > tdc is currently in Preview. Its features and command-line interface might change without prior notice. ``` -Chinese: - -```markdown -> **注意:** -> -> tdc 当前处于预览(Preview)阶段,其功能和命令行界面可能会发生变更,恕不另行通知。 -``` - -The tdc section labels in both `TOC-ai.md` files and both AI home pages must also include `(Preview)`. +The tdc section label in `TOC-ai.md` and the AI home page must also include `(Preview)`. Product feedback and bug reports must point to: @@ -44,7 +36,7 @@ Do not direct ordinary tdc feedback to the TiDB, Drive9, or PingCAP docs issue t All new tdc Markdown basenames must be globally unique within each PingCAP documentation repository, even when files are stored in different directories. Every basename must start with the `tdc-` prefix and use the existing PingCAP docs kebab-case naming convention. Do not add a generic `_index.md`, `overview.md`, `quick-start.md`, or `reference.md` under the tdc tree. -The English and Chinese repositories use the same relative paths: +The English repository uses these relative paths: ```text ai/tdc/ @@ -75,7 +67,7 @@ ai/tdc/ tdc-troubleshooting.md ``` -All internal links use absolute PingCAP documentation paths rooted at `/ai/tdc/`. English and Chinese links use the same paths; locale routing is handled by the documentation site. +All internal links use absolute PingCAP documentation paths rooted at `/ai/tdc/`. ## Documentation Style @@ -90,7 +82,7 @@ Follow the existing PingCAP documentation templates and AI documentation style: - Commands use the installed `tdc` name, never the local development path `bin/tdc`. - Examples use placeholders and synthetic resource names. Never include real API keys, FS tokens, SQL passwords, vault tokens, project IDs, cluster IDs, tenant IDs, or customer data. - Secrets should be passed through environment variables where possible. If a secret flag must be explained, warn that flags may remain in shell history or process listings. -- English is the source documentation. Chinese pages preserve the same technical meaning and command blocks but use natural Chinese technical writing rather than literal machine translation. +- English is the only published language for these TiDB Cloud pages. - Product and command names remain in their canonical form: `tdc`, TiDB Cloud Starter, TiDB Cloud Filesystem, `tdc fs`, `tdc fs-git`, `tdc fs-journal`, and `tdc fs-vault`. Do not document `make build`, `go build`, `go install`, or source compilation as an end-user installation method. Developers who need source builds can use the tdc GitHub repository. Published user installation documentation covers only supported release installers and updates. @@ -267,20 +259,18 @@ Do not document pending telemetry commands or unimplemented serverless-function/ ## Navigation -Update both files: +Update: ```text docs/pingcap-docs/docs/TOC-ai.md -docs/pingcap-docs/docs-cn/TOC-ai.md ``` Under Quick Start, Concepts, Guides, Examples, and Reference, add a nested `TiDB Cloud CLI (tdc) (Preview)` group with direct links to all corresponding tdc pages. Do not expose only the overview while leaving the other pages unreachable from the TOC. -Update both AI home pages: +Update the AI home page: ```text docs/pingcap-docs/docs/ai/_index.md -docs/pingcap-docs/docs-cn/ai/_index.md ``` Add a `TiDB Cloud CLI (tdc) (Preview)` section with concise grouped tables linking directly to every tdc page. `tdc-overview.md` is the tdc landing page; no generic `_index.md` is added under `ai/tdc/`. @@ -324,12 +314,10 @@ The two documentation directories are independent Git submodules: 1. Create an English docs branch from `release-8.5` in `docs/pingcap-docs/docs`. 2. Write and validate the English pages, `TOC-ai.md`, and AI home-page updates. -3. Create a Chinese docs branch from `release-8.5` in `docs/pingcap-docs/docs-cn`. -4. Translate the final English structure and update the Chinese TOC and AI home page. -5. Validate both submodule worktrees independently. -6. Commit changes inside each submodule. -7. Update the parent tdc repository gitlinks to the reviewed submodule commits. -8. Update the parent tdc current docs and completed-spec annotations in the same tdc change set. +3. Validate the documentation submodule worktree. +4. Commit changes inside the submodule. +5. Update the parent tdc repository gitlink to the reviewed submodule commit. +6. Update the parent tdc current docs and completed-spec annotations in the same tdc change set. Do not make the tdc build, tests, packaging, or release artifacts depend on either documentation submodule. The submodules are documentation source only. @@ -342,17 +330,16 @@ No example may depend on an undocumented endpoint, raw server URL, or direct Dri ## Dependencies And Platform - Depends on all completed MVP command specs through `0018`. -- Depends on the `pingcap/docs` and `pingcap/docs-cn` submodules tracking `release-8.5`. +- Depends on the `pingcap/docs` submodule tracking `release-8.5`. - Adds no Go package, cgo, runtime, installer, or service dependency. - Published installation instructions support release installers only. - Platform documentation must be based on current tested behavior and must distinguish macOS WebDAV from optional macFUSE/FUSE. ## Acceptance Criteria -- Every English tdc page has a matching Chinese page at the same relative path. - Every new Markdown basename starts with `tdc-` and is globally unique in its repository. - Every page includes valid front matter and the standard Preview note. -- Both TOC files directly link every tdc page under the correct category. +- `TOC-ai.md` directly links every tdc page under the correct category. - Both AI home pages directly link every tdc page. - All internal links resolve to files or approved external URLs. - The overview links feedback to `https://github.com/tidbcloud/tdc/issues`. @@ -365,7 +352,7 @@ No example may depend on an undocumented endpoint, raw server URL, or direct Dri - Examples use only synthetic values and never expose a real secret. - `docs/priciples.md`, `docs/present.md`, README, AGENTS, and the listed completed specs no longer present superseded behavior as current behavior. - Archived README snapshots and historical release notes remain unchanged. -- English and Chinese Markdown lint and link checks pass using each documentation repository's available validation workflow. +- English Markdown lint and link checks pass using the documentation repository's available validation workflow. - The parent tdc repository records clean submodule gitlinks with no uncommitted documentation changes. ## Out Of Scope diff --git a/e2e/cli_test.go b/e2e/cli_test.go index bc6fe32..a2c046f 100644 --- a/e2e/cli_test.go +++ b/e2e/cli_test.go @@ -53,6 +53,11 @@ func TestHelpAndVersion(t *testing.T) { chmodFile.wantExitCode(0) chmodFile.wantStdoutContains("--mode") + deleteFileSystem := runTDC(t, bin, "fs", "delete-file-system", "help") + deleteFileSystem.wantExitCode(0) + deleteFileSystem.wantStdoutContains("--file-system-name") + deleteFileSystem.wantStdoutNotContains("--confirm-file-system-name") + packFileSystem := runTDC(t, bin, "fs", "pack-file-system", "help") packFileSystem.wantExitCode(0) packFileSystem.wantStdoutContains("--archive-path") @@ -118,6 +123,10 @@ func TestErrorsAreRenderedAtCLIBoundary(t *testing.T) { unknown.wantExitCode(2) unknown.wantStderrContains(`tdc [ERROR]: unknown command "missing-command" for "tdc db"`) + removedConfirmation := runTDC(t, bin, "fs", "delete-file-system", "--file-system-name", "workspace", "--confirm-file-system-name", "workspace") + removedConfirmation.wantExitCode(2) + removedConfirmation.wantStderrContains(`unknown flag: --confirm-file-system-name`) + releaseServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/releases/latest" { http.NotFound(w, r) @@ -400,7 +409,7 @@ func TestFSResourceRegistrySelectionAcrossCommandFamilies(t *testing.T) { assertFakeDrive9Call(t, calls, []string{"git", "hydrate"}, "key-scratch", home, "stage", "scratch", "https://fs-west.test", "aws-us-west-2") assertFakeDrive9Call(t, calls, []string{"mount"}, "key-scratch", home, "stage", "scratch", "https://fs-west.test", "aws-us-west-2") - deleteScratch := runTDCWithInput(t, bin, "", baseEnv, "--profile", "stage", "fs", "delete-file-system", "--file-system-name", "scratch", "--confirm-file-system-name", "scratch") + deleteScratch := runTDCWithInput(t, bin, "", baseEnv, "--profile", "stage", "fs", "delete-file-system", "--file-system-name", "scratch") deleteScratch.wantExitCode(0) deleteScratch.wantStdoutContains(`"status": "deleting"`) afterDelete := runTDCWithInput(t, bin, "", baseEnv, "--profile", "stage", "fs", "list-file-systems") diff --git a/e2e/live_test.go b/e2e/live_test.go index 88fdfe6..d88a171 100644 --- a/e2e/live_test.go +++ b/e2e/live_test.go @@ -99,7 +99,7 @@ func TestLiveFSResourceRegistryLifecycle(t *testing.T) { if !created[name] { continue } - result := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", name, "--confirm-file-system-name", name) + result := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", name) if result.exitCode != 0 { t.Logf("cleanup delete failed for tdc fs resource %q: exit=%d stdout=%s stderr=%s", name, result.exitCode, result.stdout, result.stderr) } @@ -148,7 +148,7 @@ func TestLiveFSResourceRegistryLifecycle(t *testing.T) { explicitCheck.wantExitCode(0) explicitCheck.wantStdoutContains(`"file_system_name": "` + names[1] + `"`) - deleteFirst := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", names[0], "--confirm-file-system-name", names[0]) + deleteFirst := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", names[0]) deleteFirst.wantExitCode(0) deleteFirst.wantStdoutContains(`"status": "deleting"`) deleteFirst.wantStdoutContains(`"remote_deletion_state": "deleting"`) @@ -157,7 +157,7 @@ func TestLiveFSResourceRegistryLifecycle(t *testing.T) { remaining.wantExitCode(0) remaining.wantStdoutContains(`"file_system_name": "` + names[1] + `"`) - deleteSecond := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", names[1], "--confirm-file-system-name", names[1]) + deleteSecond := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", names[1]) deleteSecond.wantExitCode(0) deleteSecond.wantStdoutContains(`"status": "deleting"`) deleteSecond.wantStdoutContains(`"remote_deletion_state": "deleting"`) @@ -291,7 +291,7 @@ func TestLiveFSCommandSurface(t *testing.T) { }) testLiveMutatingDryRuns(t, bin, profileName, [][]string{ {"fs", "create-file-system", "--file-system-name", fileSystemName, "--wait"}, - {"fs", "delete-file-system", "--file-system-name", fileSystemName, "--confirm-file-system-name", fileSystemName}, + {"fs", "delete-file-system", "--file-system-name", fileSystemName}, {"fs", "create-layer", "--layer-id", "layer-1", "--base-root-path", "/workspace", "--layer-name", "dev"}, {"fs", "create-layer-checkpoint", "--layer-id", "layer-1", "--checkpoint-id", "cp-1"}, {"fs", "rollback-layer", "--layer-id", "layer-1"}, {"fs", "commit-layer", "--layer-id", "layer-1"}, @@ -1260,7 +1260,7 @@ func TestLiveFSConfigurationFreeAccess(t *testing.T) { if deletedResource { return } - cleanup := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", fileSystemName, "--confirm-file-system-name", fileSystemName) + cleanup := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", fileSystemName) if cleanup.exitCode != 0 { t.Logf("cleanup configuration-free FS resource failed for %q: exit=%d stderr=%s", fileSystemName, cleanup.exitCode, strings.TrimSpace(cleanup.stderr)) } @@ -1363,7 +1363,7 @@ func TestLiveFSConfigurationFreeAccess(t *testing.T) { deleteRemote := runTDC(t, bin, "--profile", profileName, "fs", "delete-file", "--file-system-name", fileSystemName, "--path", remoteRoot, "--recursive") deleteRemote.wantExitCode(0) remoteDeleted = true - deleteResource := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", fileSystemName, "--confirm-file-system-name", fileSystemName) + deleteResource := runTDC(t, bin, "--profile", profileName, "fs", "delete-file-system", "--file-system-name", fileSystemName) deleteResource.wantExitCode(0) deleteResource.wantStdoutContains(`"status": "deleting"`) deleteResource.wantStdoutContains(`"remote_deletion_state": "deleting"`) @@ -2049,7 +2049,6 @@ func cleanupAutoCreatedLiveFSResource() { "--profile", profileName, "fs", "delete-file-system", "--file-system-name", name, - "--confirm-file-system-name", name, ) output, err := cmd.CombinedOutput() if err != nil { @@ -2071,7 +2070,6 @@ func releaseAutoCreatedLiveFSResource(t *testing.T, bin, profileName string) { "--profile", profileName, "fs", "delete-file-system", "--file-system-name", name, - "--confirm-file-system-name", name, ) result.wantExitCode(0) liveFSResourceAutoCreated = false diff --git a/internal/cli/commands.go b/internal/cli/commands.go index 4dc5dd9..9d657a3 100644 --- a/internal/cli/commands.go +++ b/internal/cli/commands.go @@ -1093,14 +1093,13 @@ func newFSDeleteFileSystemCommand(info version.Info) *cobra.Command { if err != nil { return nil, err } - name, confirmName, err := fsDeleteFlags(ctx) + name, err := fsDeleteFileSystemName(ctx) if err != nil { return nil, err } return service.DeleteFileSystem(ctx.cmd.Context(), tdcfs.DeleteFileSystemOptions{ - Profile: profile, - FileSystemName: name, - ConfirmFileSystemName: confirmName, + Profile: profile, + FileSystemName: name, }) }, DryRun: func(ctx commandContext) (dryrun.Result, error) { @@ -1108,20 +1107,18 @@ func newFSDeleteFileSystemCommand(info version.Info) *cobra.Command { if err != nil { return dryrun.Result{}, err } - name, confirmName, err := fsDeleteFlags(ctx) + name, err := fsDeleteFileSystemName(ctx) if err != nil { return dryrun.Result{}, err } return service.DryRunDeleteFileSystem(ctx.cmd.Context(), ctx.CommandPath(), tdcfs.DeleteFileSystemOptions{ - Profile: profile, - FileSystemName: name, - ConfirmFileSystemName: confirmName, + Profile: profile, + FileSystemName: name, }) }, }, info) cmd.Flags().String("file-system-name", "", "tdc fs resource name") - cmd.Flags().String("confirm-file-system-name", "", "required exact tdc fs resource name confirmation") - markUsageRequired(cmd, "file-system-name", "confirm-file-system-name") + markUsageRequired(cmd, "file-system-name") return cmd } @@ -2551,16 +2548,12 @@ func fsResolveAuthenticatedProfile(ctx commandContext, profile *config.Profile, return selected, nil } -func fsDeleteFlags(ctx commandContext) (string, string, error) { +func fsDeleteFileSystemName(ctx commandContext) (string, error) { name, err := ctx.StringFlag("file-system-name") if err != nil { - return "", "", err - } - confirmName, err := ctx.StringFlag("confirm-file-system-name") - if err != nil { - return "", "", err + return "", err } - return name, confirmName, nil + return name, nil } func newFSVaultCommand(info version.Info) *cobra.Command { diff --git a/internal/fs/control.go b/internal/fs/control.go index ea9016f..99ddafb 100644 --- a/internal/fs/control.go +++ b/internal/fs/control.go @@ -47,9 +47,8 @@ type CreateFileSystemOptions struct { } type DeleteFileSystemOptions struct { - Profile *config.Profile - FileSystemName string - ConfirmFileSystemName string + Profile *config.Profile + FileSystemName string } type CheckFileSystemOptions struct { @@ -204,7 +203,6 @@ func (s Service) DryRunDeleteFileSystem(ctx context.Context, commandPath string, checks := []dryrun.Check{ {Name: "config_and_credentials", Status: "passed", Message: fmt.Sprintf("profile %q loaded", profileName(opts.Profile))}, {Name: "permission_requirement", Status: "passed", Message: string(authz.FSVolumeDelete)}, - {Name: "delete_confirmation", Status: "passed", Message: opts.ConfirmFileSystemName}, } if resource := fscred.FromProfile(opts.Profile); resource.HasAPIKey { checks = append(checks, dryrun.Check{Name: "fs_resource_credentials", Status: "passed", Message: name}) @@ -236,7 +234,7 @@ func (s Service) DryRunDeleteFileSystem(ctx context.Context, commandPath string, Method: http.MethodDelete, Path: "/v1/tenant", Body: redactedDeprovisionRequest(body), - Description: "normal execution verifies --confirm-file-system-name and uses the stored tdc fs API key before deleting", + Description: "normal execution uses the stored tdc fs API key before deleting", }, checks..., ), nil @@ -289,12 +287,6 @@ func (s Service) deleteDryRunInputs(opts DeleteFileSystemOptions) (string, endpo if err != nil { return "", endpoints.Endpoint{}, nil, err } - if strings.TrimSpace(opts.ConfirmFileSystemName) == "" { - return "", endpoints.Endpoint{}, nil, apperr.New("fs.missing_confirmation", "usage", 2, "--confirm-file-system-name is required") - } - if strings.TrimSpace(opts.ConfirmFileSystemName) != name { - return "", endpoints.Endpoint{}, nil, apperr.New("fs.delete_confirmation_mismatch", "usage", 2, fmt.Sprintf("--confirm-file-system-name must match --file-system-name %q", name)) - } if opts.Profile.FSResourceName != name { return "", endpoints.Endpoint{}, nil, resourceMismatch(opts.Profile.FSResourceName, name) } diff --git a/internal/fs/drive9_companion_test.go b/internal/fs/drive9_companion_test.go index 4e5fdd2..aa34a82 100644 --- a/internal/fs/drive9_companion_test.go +++ b/internal/fs/drive9_companion_test.go @@ -257,9 +257,8 @@ func TestDrive9DeleteFileSystemDeletesOnlySelectedRegistryResource(t *testing.T) profile.FSDefaultFileSystemName = "workspace" result, err := testCompanionService(home, companion).DeleteFileSystem(context.Background(), DeleteFileSystemOptions{ - Profile: profile, - FileSystemName: "workspace", - ConfirmFileSystemName: "workspace", + Profile: profile, + FileSystemName: "workspace", }) if err != nil { t.Fatalf("DeleteFileSystem failed: %v", err) @@ -595,9 +594,8 @@ func TestDryRunDeleteFileSystemReportsRegistryFiles(t *testing.T) { } profile.FSDefaultFileSystemName = "workspace" result, err := (Service{HomeDir: home, Resolver: supportedFSManifestResolver("https://fs.test")}).DryRunDeleteFileSystem(context.Background(), "tdc fs delete-file-system", DeleteFileSystemOptions{ - Profile: profile, - FileSystemName: "workspace", - ConfirmFileSystemName: "workspace", + Profile: profile, + FileSystemName: "workspace", }) if err != nil { t.Fatalf("DryRunDeleteFileSystem failed: %v", err) From 5ec18485f9894d537049b0aaf973c1cd87e6547b Mon Sep 17 00:00:00 2001 From: Cheese Date: Tue, 21 Jul 2026 15:24:01 +0800 Subject: [PATCH 2/4] feat: require a root command --- AGENTS.md | 2 ++ README.md | 2 +- docs/spec/done/0001-cli-foundation.md | 5 ++++- e2e/cli_test.go | 11 ++++++++++ internal/cli/root.go | 20 +++++++++++++++++- internal/cli/root_test.go | 30 +++++++++++++++++++++++++++ 6 files changed, 67 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 5955b17..75cc2cc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -376,6 +376,8 @@ Follow these rules unless `docs/priciples.md` is updated: Implemented command behavior: +- `tdc` without a command returns `cli.missing_command` with exit code `2` and + an AWS-style compact two-level usage synopsis on stderr - `tdc configure` - `tdc configure --non-interactive` - `tdc help` diff --git a/README.md b/README.md index c75c170..d2776fc 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Each project includes a `type`: `tidbx` identifies a regular project and `tidbx_ ## Commands -Run `tdc help`, `tdc help`, or `tdc help` for flags and examples. +Running `tdc` without a command returns a usage error and a compact two-level command synopsis. Run `tdc help`, `tdc help`, or `tdc help` for the full command list, flags, and examples.
All commands diff --git a/docs/spec/done/0001-cli-foundation.md b/docs/spec/done/0001-cli-foundation.md index 03a3327..1f59ad5 100644 --- a/docs/spec/done/0001-cli-foundation.md +++ b/docs/spec/done/0001-cli-foundation.md @@ -38,6 +38,9 @@ on. include component-specific version details when a component has them. - Unknown commands and flags must return non-zero exit codes with actionable errors. +- Running `tdc` without a command returns the `cli.missing_command` usage error + with exit code `2` and a compact two-level command synopsis. Users can run + `tdc help` for the complete command and flag listing. - Help text must be deterministic and suitable for agents to parse. ## Inputs And Config @@ -112,7 +115,7 @@ rendering are local-only. - `tdc help`, root/command/subcommand `--version`, and nested help commands work. - No command exposes short flags. -- Tests cover unknown command handling and help availability. +- Tests cover missing and unknown command handling and help availability. ## Out Of Scope diff --git a/e2e/cli_test.go b/e2e/cli_test.go index a2c046f..c2dc263 100644 --- a/e2e/cli_test.go +++ b/e2e/cli_test.go @@ -19,6 +19,17 @@ import ( func TestHelpAndVersion(t *testing.T) { bin := tdcBinary(t) + missingCommand := runTDC(t, bin) + missingCommand.wantExitCode(2) + if missingCommand.stdout != "" { + missingCommand.fail("stdout should be empty") + } + missingCommand.wantStderrContains("tdc [ERROR]: the following arguments are required: command") + missingCommand.wantStderrContains("The TiDB Cloud Command Line Interface is a unified tool") + missingCommand.wantStderrContains("usage: tdc [ ...] [parameters]") + missingCommand.wantStderrContains("tdc help") + missingCommand.wantStderrNotContains("Commands:") + root := runTDC(t, bin, "help") root.wantExitCode(0) root.wantStdoutContains("Commands:") diff --git a/internal/cli/root.go b/internal/cli/root.go index a5cbc67..8340d86 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -45,7 +45,7 @@ func NewRootCommand(info version.Info) *cobra.Command { Short: "CLI for TiDB Cloud Filesystem (FS) and TiDB Cloud Starter.", Long: "The TiDB Cloud Command Line Interface is a unified tool to manage your TiDB Cloud Filesystem (FS) and Starter services.", RunE: func(cmd *cobra.Command, _ []string) error { - return cmd.Help() + return rootCommandRequiredError(cmd) }, }, info) @@ -82,6 +82,24 @@ func NewRootCommand(info version.Info) *cobra.Command { return root } +func rootCommandRequiredError(cmd *cobra.Command) error { + return apperr.New( + "cli.missing_command", + "usage", + 2, + fmt.Sprintf(`the following arguments are required: command + +%s + +usage: tdc [ ...] [parameters] +To see help information, you can run: + + tdc help + tdc help + tdc help`, cmd.Long), + ) +} + func Execute(ctx context.Context, root *cobra.Command, args []string, stdout, stderr io.Writer) error { recorder := commandRecorder() ctx = oplog.WithRecorder(ctx, recorder) diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go index fc0be9e..0348ff1 100644 --- a/internal/cli/root_test.go +++ b/internal/cli/root_test.go @@ -61,6 +61,36 @@ func TestHelpCommands(t *testing.T) { } } +func TestRootRequiresCommand(t *testing.T) { + stdout, stderr, err := executeForTest() + if err == nil { + t.Fatal("expected root command without arguments to fail") + } + if stdout != "" { + t.Fatalf("expected no stdout, got %q", stdout) + } + if stderr != "" { + t.Fatalf("expected CLI boundary to leave error rendering to main, got %q", stderr) + } + if got := apperr.CodeFor(err); got != "cli.missing_command" { + t.Fatalf("expected cli.missing_command, got %q", got) + } + if got := apperr.ExitCodeFor(err); got != 2 { + t.Fatalf("expected exit code 2, got %d", got) + } + message := apperr.MessageFor(err) + for _, want := range []string{ + "the following arguments are required: command", + "The TiDB Cloud Command Line Interface is a unified tool", + "usage: tdc [ ...] [parameters]", + "tdc help", + } { + if !strings.Contains(message, want) { + t.Fatalf("expected root usage error to contain %q, got:\n%s", want, message) + } + } +} + func TestVersionWorksAtEveryLevel(t *testing.T) { tests := [][]string{ {"--version"}, From 4bddd395fa145dd5781a7cd258063326eaf56aa4 Mon Sep 17 00:00:00 2001 From: Cheese Date: Tue, 21 Jul 2026 17:15:15 +0800 Subject: [PATCH 3/4] fix: reflect the two-level command tree --- e2e/cli_test.go | 2 +- internal/cli/root.go | 2 +- internal/cli/root_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/e2e/cli_test.go b/e2e/cli_test.go index c2dc263..3050366 100644 --- a/e2e/cli_test.go +++ b/e2e/cli_test.go @@ -26,7 +26,7 @@ func TestHelpAndVersion(t *testing.T) { } missingCommand.wantStderrContains("tdc [ERROR]: the following arguments are required: command") missingCommand.wantStderrContains("The TiDB Cloud Command Line Interface is a unified tool") - missingCommand.wantStderrContains("usage: tdc [ ...] [parameters]") + missingCommand.wantStderrContains("usage: tdc [] [parameters]") missingCommand.wantStderrContains("tdc help") missingCommand.wantStderrNotContains("Commands:") diff --git a/internal/cli/root.go b/internal/cli/root.go index 8340d86..9d42329 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -91,7 +91,7 @@ func rootCommandRequiredError(cmd *cobra.Command) error { %s -usage: tdc [ ...] [parameters] +usage: tdc [] [parameters] To see help information, you can run: tdc help diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go index 0348ff1..1ccbebd 100644 --- a/internal/cli/root_test.go +++ b/internal/cli/root_test.go @@ -82,7 +82,7 @@ func TestRootRequiresCommand(t *testing.T) { for _, want := range []string{ "the following arguments are required: command", "The TiDB Cloud Command Line Interface is a unified tool", - "usage: tdc [ ...] [parameters]", + "usage: tdc [] [parameters]", "tdc help", } { if !strings.Contains(message, want) { From 7cdd562975e54bdb8ba468b26662a6b9b52d8c4a Mon Sep 17 00:00:00 2001 From: Cheese Date: Tue, 21 Jul 2026 18:09:32 +0800 Subject: [PATCH 4/4] feat: clarify command help and errors --- AGENTS.md | 3 + README.md | 2 +- cmd/tdc/main.go | 2 +- docs/spec/done/0001-cli-foundation.md | 2 + e2e/cli_test.go | 17 +++++- internal/cli/commands.go | 2 +- internal/cli/root.go | 77 ++++++++++++++++++++++-- internal/cli/root_test.go | 15 ++++- internal/config/configure/wizard.go | 2 +- internal/config/configure/wizard_test.go | 3 + 10 files changed, 115 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 75cc2cc..2d26dcb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -350,6 +350,9 @@ Follow these rules unless `docs/priciples.md` is updated: - `tdc help` - `tdc help` - `tdc help` +- In generated Flags and Global Flags sections, render value types as + `` and append `(required)` to flags marked with + `tdc_usage_required`. - Keep the global `--version` behavior intact at every command level. Do not add command-specific `--version ` flags; use names such as `--target-version` when a command needs a version input. diff --git a/README.md b/README.md index d2776fc..f3cb838 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ Each project includes a `type`: `tidbx` identifies a regular project and `tidbx_ ## Commands -Running `tdc` without a command returns a usage error and a compact two-level command synopsis. Run `tdc help`, `tdc help`, or `tdc help` for the full command list, flags, and examples. +Running `tdc` without a command returns a usage error and a compact two-level command synopsis. Run `tdc help`, `tdc help`, or `tdc help` for the full command list, flags, and examples. Help displays flag value types in angle brackets and marks required flags with `(required)`.
All commands diff --git a/cmd/tdc/main.go b/cmd/tdc/main.go index 9513ea9..ad581ec 100644 --- a/cmd/tdc/main.go +++ b/cmd/tdc/main.go @@ -17,7 +17,7 @@ func main() { root := cli.NewRootCommand(version.Current()) if err := cli.Execute(ctx, root, os.Args[1:], os.Stdout, os.Stderr); err != nil { - fmt.Fprintf(os.Stderr, "tdc [ERROR]: %s\n", apperr.MessageFor(err)) + fmt.Fprintf(os.Stderr, "\ntdc [ERROR]: %s\n", apperr.MessageFor(err)) os.Exit(apperr.ExitCodeFor(err)) } } diff --git a/docs/spec/done/0001-cli-foundation.md b/docs/spec/done/0001-cli-foundation.md index 1f59ad5..47399e8 100644 --- a/docs/spec/done/0001-cli-foundation.md +++ b/docs/spec/done/0001-cli-foundation.md @@ -42,6 +42,8 @@ on. with exit code `2` and a compact two-level command synopsis. Users can run `tdc help` for the complete command and flag listing. - Help text must be deterministic and suitable for agents to parse. +- Flags and Global Flags sections render value types as `` and append + `(required)` after flags declared required by the command contract. ## Inputs And Config diff --git a/e2e/cli_test.go b/e2e/cli_test.go index 3050366..e48d65c 100644 --- a/e2e/cli_test.go +++ b/e2e/cli_test.go @@ -29,13 +29,16 @@ func TestHelpAndVersion(t *testing.T) { missingCommand.wantStderrContains("usage: tdc [] [parameters]") missingCommand.wantStderrContains("tdc help") missingCommand.wantStderrNotContains("Commands:") + if !strings.HasPrefix(missingCommand.stderr, "\ntdc [ERROR]:") { + missingCommand.fail("stderr should start with a blank line followed by the error prefix") + } root := runTDC(t, bin, "help") root.wantExitCode(0) root.wantStdoutContains("Commands:") root.wantStdoutContains("db") - root.wantStdoutContains("--region string") root.wantStdoutNotContains("-h,") + root.wantStdoutContains("--region ") db := runTDC(t, bin, "db", "help") db.wantExitCode(0) @@ -69,6 +72,18 @@ func TestHelpAndVersion(t *testing.T) { deleteFileSystem.wantStdoutContains("--file-system-name") deleteFileSystem.wantStdoutNotContains("--confirm-file-system-name") + createDBCluster := runTDC(t, bin, "db", "create-db-cluster", "help") + createDBCluster.wantExitCode(0) + createDBCluster.wantStdoutContains("--db-cluster-name (required)") + createDBCluster.wantStdoutContains("--db-cluster-type (required)") + createDBCluster.wantStdoutContains("--project-id ") + createDBCluster.wantStdoutNotContains("--project-id (required)") + + configure := runTDC(t, bin, "configure", "help") + configure.wantExitCode(0) + configure.wantStdoutContains("--region-code ") + configure.wantStdoutContains("Default region code") + packFileSystem := runTDC(t, bin, "fs", "pack-file-system", "help") packFileSystem.wantExitCode(0) packFileSystem.wantStdoutContains("--archive-path") diff --git a/internal/cli/commands.go b/internal/cli/commands.go index 9d657a3..e593483 100644 --- a/internal/cli/commands.go +++ b/internal/cli/commands.go @@ -76,7 +76,7 @@ func newConfigureCommand(info version.Info) *cobra.Command { return renderStructured(cmd, result) }, }, info) - cmd.Flags().String("region-code", "", "TiDB Cloud region code, for example aws-us-east-1 or aws-ap-southeast-1.") + cmd.Flags().String("region-code", "", "Default region code, for example aws-us-east-1 or aws-ap-southeast-1.") cmd.Flags().String("tdc-public-key", "", "TiDB Cloud API public key.") cmd.Flags().String("tdc-private-key", "", "TiDB Cloud API private key.") cmd.Flags().Bool("non-interactive", false, "Use this option to avoid being prompted for configuration values. You must provide at least three configuration values (--tdc-public-key, --tdc-private-key, and --region-code) when using this option. This is useful when running tdc in a script or automated environment.") diff --git a/internal/cli/root.go b/internal/cli/root.go index 9d42329..975ba41 100644 --- a/internal/cli/root.go +++ b/internal/cli/root.go @@ -27,6 +27,7 @@ const usageRequiredFlagAnnotation = "tdc_usage_required" func init() { cobra.AddTemplateFunc("usageSynopsis", usageSynopsis) + cobra.AddTemplateFunc("formattedFlagUsages", formattedFlagUsages) } type Options struct { @@ -734,6 +735,74 @@ func usageFlagValueType(flag *pflag.Flag) string { } } +func formattedFlagUsages(flags *pflag.FlagSet) string { + if flags == nil { + return "" + } + type flagUsage struct { + label string + description string + } + lines := make([]flagUsage, 0) + maxLabelLength := 0 + flags.VisitAll(func(flag *pflag.Flag) { + if flag == nil || flag.Hidden { + return + } + label := " " + usageFlagSegment(flag) + if usageFlagRequired(flag) { + label += " (required)" + } + _, description := pflag.UnquoteUsage(flag) + description += formattedFlagDefault(flag) + if flag.Deprecated != "" { + description += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated) + } + lines = append(lines, flagUsage{label: label, description: description}) + if len(label) > maxLabelLength { + maxLabelLength = len(label) + } + }) + + var result strings.Builder + for _, line := range lines { + fmt.Fprintf(&result, "%-*s %s\n", maxLabelLength, line.label, line.description) + } + return result.String() +} + +func formattedFlagDefault(flag *pflag.Flag) string { + if flag == nil || flagDefaultIsZero(flag) { + return "" + } + if flag.Value.Type() == "string" { + return fmt.Sprintf(" (default %q)", flag.DefValue) + } + return fmt.Sprintf(" (default %s)", flag.DefValue) +} + +func flagDefaultIsZero(flag *pflag.Flag) bool { + if flag == nil || flag.Value == nil { + return true + } + switch flag.Value.Type() { + case "bool", "boolfunc": + return flag.DefValue == "false" || flag.DefValue == "" + case "duration": + return flag.DefValue == "0" || flag.DefValue == "0s" + case "int", "int8", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64", "count", "float32", "float64": + return flag.DefValue == "0" + case "string": + return flag.DefValue == "" + case "ip", "ipMask", "ipNet": + return flag.DefValue == "" + case "intSlice", "stringSlice", "stringArray", "uintSlice", "boolSlice": + return flag.DefValue == "[]" + default: + return flag.DefValue == "false" || flag.DefValue == "" || flag.DefValue == "" || flag.DefValue == "0" + } +} + const helpTemplate = `{{with (or .Long .Short)}}{{.}} {{end}}Usage: @@ -748,10 +817,10 @@ Commands: {{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} +{{formattedFlagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} Global Flags: -{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}} +{{formattedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}{{end}} ` const usageTemplate = `Usage: @@ -759,8 +828,8 @@ const usageTemplate = `Usage: {{.CommandPath}} help [command]{{end}}{{if .HasAvailableLocalFlags}} Flags: -{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} +{{formattedFlagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} Global Flags: -{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}} +{{formattedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}{{end}} ` diff --git a/internal/cli/root_test.go b/internal/cli/root_test.go index 1ccbebd..fc8c28b 100644 --- a/internal/cli/root_test.go +++ b/internal/cli/root_test.go @@ -215,7 +215,7 @@ func TestHelpOutputDoesNotExposeShortFlags(t *testing.T) { if strings.Contains(stdout, "-h,") || strings.Contains(stdout, "-v,") { t.Fatalf("help output exposes a short flag:\n%s", stdout) } - if !strings.Contains(stdout, "--region string") { + if !strings.Contains(stdout, "--region ") { t.Fatalf("help output does not include global --region flag:\n%s", stdout) } } @@ -260,6 +260,19 @@ func TestHelpUsageShowsRequiredFirstAndOptionalBracketed(t *testing.T) { if !strings.Contains(stdout, " [--wait]") { t.Fatalf("expected --wait to be optional, got:\n%s", stdout) } + for _, want := range []string{ + "--db-cluster-name (required)", + "--db-cluster-type (required)", + "--project-id ", + "--monthly-spending-limit-usd-cents ", + } { + if !strings.Contains(stdout, want) { + t.Fatalf("expected formatted flag %q, got:\n%s", want, stdout) + } + } + if strings.Contains(stdout, "--project-id (required)") { + t.Fatalf("optional --project-id must not be marked required, got:\n%s", stdout) + } stdout, _, err = executeForTest("db", "delete-db-cluster", "help") if err != nil { diff --git a/internal/config/configure/wizard.go b/internal/config/configure/wizard.go index aa6da5a..9db7385 100644 --- a/internal/config/configure/wizard.go +++ b/internal/config/configure/wizard.go @@ -86,7 +86,7 @@ func Run(ctx context.Context, opts Options) (Result, error) { } defaultRegion := region.DefaultPlacementCode() - regionCode, err := valueOrPrompt(ctx, input, opts.Out, valueOrEnv(opts.RegionCode, opts.Env, "TDC_REGION_CODE"), "region code", fmt.Sprintf("Region code (%s): ", defaultRegion), defaultRegion, false, opts.NonInteractive) + regionCode, err := valueOrPrompt(ctx, input, opts.Out, valueOrEnv(opts.RegionCode, opts.Env, "TDC_REGION_CODE"), "default region code", fmt.Sprintf("Default region code (%s): ", defaultRegion), defaultRegion, false, opts.NonInteractive) if err != nil { return Result{}, err } diff --git a/internal/config/configure/wizard_test.go b/internal/config/configure/wizard_test.go index 01f7475..2a7714f 100644 --- a/internal/config/configure/wizard_test.go +++ b/internal/config/configure/wizard_test.go @@ -36,6 +36,9 @@ func TestRunWritesProfileAndDoesNotPrintSecret(t *testing.T) { if strings.Contains(output.String(), "private-key") { t.Fatalf("configure output leaked private key:\n%s", output.String()) } + if !strings.Contains(output.String(), "Default region code") { + t.Fatalf("configure output missing default region prompt:\n%s", output.String()) + } if result.ProjectID != "virtual-1" || result.ProjectType != virtualProjectType || !result.CredentialsStored { t.Fatalf("unexpected configure result: %#v", result) }