From da21ac2f21e44f4e04866ccf2f6b1a9f2d484bb6 Mon Sep 17 00:00:00 2001 From: 4ier Date: Thu, 30 Apr 2026 14:00:04 +0800 Subject: [PATCH] feat(page): make 'archive' canonical, keep 'delete'/'trash' as aliases Three changes to de-scare the soft-delete command: - 'page archive ' is the canonical form. This is the term used in Notion's UI and in the 2025 API (where the field is renamed in_trash, kept backwards-compatible with 'archived'). - 'page delete' and 'page trash' are now cobra aliases that resolve to the same RunE. Existing scripts pinned on 'page delete' keep working unchanged; the legacy variable pageDeleteCmd also still exists as an assignment to pageArchiveCmd for any external code path. - Help text now makes the soft-delete nature explicit and the success line tells the user how to undo ('run notion page restore'). The page restore Long was updated to say 'reverse of archive / delete / trash' for symmetry. Aliases are hidden from 'notion page --help' by cobra, so new users see one canonical command; they surface on 'notion page --help'. Closes #35 --- cmd/page.go | 33 ++++++++++++++++++++---------- cmd/page_archive_test.go | 43 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 cmd/page_archive_test.go diff --git a/cmd/page.go b/cmd/page.go index 983bcde..18033f5 100644 --- a/cmd/page.go +++ b/cmd/page.go @@ -323,14 +323,22 @@ Examples: }, } -var pageDeleteCmd = &cobra.Command{ - Use: "delete ", - Short: "Delete (archive) a page", - Long: `Archive a Notion page (soft delete). +var pageArchiveCmd = &cobra.Command{ + Use: "archive ", + Aliases: []string{"delete", "trash"}, + Short: "Archive a page (soft delete, reversible)", + Long: `Archive a Notion page. + +This is a soft delete — the page moves to the workspace trash but can be +brought back with 'notion page restore'. Despite the legacy 'delete' +alias, no content is removed permanently. + +Aliases: archive | delete | trash Examples: - notion page delete abc123 - notion page delete https://notion.so/My-Page-abc123`, + notion page archive abc123 + notion page trash https://notion.so/My-Page-abc123 + notion page delete abc123 # still works for back-compat`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { token, err := getToken() @@ -348,7 +356,7 @@ Examples: data, err := c.Patch("/v1/pages/"+pageID, body) if err != nil { - return fmt.Errorf("delete page: %w", err) + return fmt.Errorf("archive page: %w", err) } if outputFormat == "json" { @@ -359,11 +367,16 @@ Examples: return render.JSON(result) } - fmt.Println("✓ Page archived") + fmt.Println("✓ Page archived (run 'notion page restore' to undo)") return nil }, } +// pageDeleteCmd is kept as a standalone var pointing at the same RunE +// purely so existing tests / scripts that reference the variable name +// still compile. The user-facing command is aliased via pageArchiveCmd. +var pageDeleteCmd = pageArchiveCmd + var pageMoveCmd = &cobra.Command{ Use: "move ", Short: "Move a page to a new parent", @@ -573,7 +586,7 @@ Examples: var pageRestoreCmd = &cobra.Command{ Use: "restore ", Short: "Restore an archived page", - Long: `Unarchive a Notion page (reverse of delete). + Long: `Unarchive a Notion page (reverse of archive / delete / trash). Examples: notion page restore abc123`, @@ -943,7 +956,7 @@ func init() { pageCmd.AddCommand(pageViewCmd) pageCmd.AddCommand(pageListCmd) pageCmd.AddCommand(pageCreateCmd) - pageCmd.AddCommand(pageDeleteCmd) + pageCmd.AddCommand(pageArchiveCmd) pageCmd.AddCommand(pageRestoreCmd) pageCmd.AddCommand(pageMoveCmd) pageCmd.AddCommand(pageOpenCmd) diff --git a/cmd/page_archive_test.go b/cmd/page_archive_test.go new file mode 100644 index 0000000..34011b1 --- /dev/null +++ b/cmd/page_archive_test.go @@ -0,0 +1,43 @@ +package cmd + +import "testing" + +func TestPageArchiveCommandAliases(t *testing.T) { + // The canonical command is `archive`; `delete` and `trash` are aliases. + wantAliases := map[string]bool{"delete": true, "trash": true} + got := map[string]bool{} + for _, a := range pageArchiveCmd.Aliases { + got[a] = true + } + if len(got) != len(wantAliases) { + t.Errorf("aliases = %v, want %v", pageArchiveCmd.Aliases, []string{"delete", "trash"}) + } + for a := range wantAliases { + if !got[a] { + t.Errorf("missing alias %q", a) + } + } +} + +func TestPageArchiveCommandNameIsCanonical(t *testing.T) { + // `Use:` should start with "archive", making that the primary term in + // --help output. The old "delete" lives only as an alias. + if got := pageArchiveCmd.Name(); got != "archive" { + t.Errorf("canonical name = %q, want 'archive'", got) + } +} + +func TestPageArchiveResolvesViaAlias(t *testing.T) { + // Cobra resolves an alias by matching against command.Aliases. + // Each alias must route to the same cobra.Command. + for _, alias := range []string{"delete", "trash", "archive"} { + cmd, _, err := pageCmd.Find([]string{alias}) + if err != nil { + t.Errorf("alias %q: Find returned error: %v", alias, err) + continue + } + if cmd != pageArchiveCmd { + t.Errorf("alias %q resolved to %q, want the archive command", alias, cmd.Name()) + } + } +}