From 8d8271b552a7025f8a7cd59be3fb4e69bc5a04af Mon Sep 17 00:00:00 2001 From: zhengzhijie Date: Thu, 2 Jul 2026 13:43:45 +0800 Subject: [PATCH] feat(sheets): add undo shortcut --- shortcuts/sheets/data/flag-defs.json | 50 ++++-------------------- shortcuts/sheets/flag_defs_gen.go | 8 ++++ shortcuts/sheets/lark_sheet_undo.go | 47 ++++++++++++++++++++++ shortcuts/sheets/lark_sheet_undo_test.go | 43 ++++++++++++++++++++ shortcuts/sheets/shortcuts.go | 1 + 5 files changed, 107 insertions(+), 42 deletions(-) create mode 100644 shortcuts/sheets/lark_sheet_undo.go create mode 100644 shortcuts/sheets/lark_sheet_undo_test.go diff --git a/shortcuts/sheets/data/flag-defs.json b/shortcuts/sheets/data/flag-defs.json index 8e8e544c3..c123aae28 100644 --- a/shortcuts/sheets/data/flag-defs.json +++ b/shortcuts/sheets/data/flag-defs.json @@ -80,32 +80,6 @@ } ] }, - "+revision-get": { - "risk": "read", - "flags": [ - { - "name": "url", - "kind": "public", - "type": "string", - "required": "xor", - "desc": "Spreadsheet locator" - }, - { - "name": "spreadsheet-token", - "kind": "public", - "type": "string", - "required": "xor", - "desc": "Spreadsheet locator" - }, - { - "name": "dry-run", - "kind": "system", - "type": "bool", - "required": "optional", - "desc": "" - } - ] - }, "+sheet-create": { "risk": "write", "flags": [ @@ -4950,37 +4924,29 @@ } ] }, - "+changeset-get": { - "risk": "read", + "+undo": { + "risk": "write", "flags": [ { "name": "url", "kind": "public", "type": "string", "required": "xor", - "desc": "Spreadsheet URL (XOR with `--spreadsheet-token`)" + "desc": "Spreadsheet locator" }, { "name": "spreadsheet-token", "kind": "public", "type": "string", "required": "xor", - "desc": "Spreadsheet token (XOR with `--url`)" + "desc": "Spreadsheet locator" }, { - "name": "start-revision", - "kind": "own", - "type": "int", - "required": "required", - "desc": "Start version (CS revision); the before baseline for review (must be >= 1)" - }, - { - "name": "end-revision", - "kind": "own", - "type": "int", + "name": "dry-run", + "kind": "system", + "type": "bool", "required": "optional", - "desc": "End version (CS revision); defaults to the latest revision. Gap (end-start+1) must be <= 20", - "default": "-1" + "desc": "" } ] } diff --git a/shortcuts/sheets/flag_defs_gen.go b/shortcuts/sheets/flag_defs_gen.go index 54060793b..a3e9ff91a 100644 --- a/shortcuts/sheets/flag_defs_gen.go +++ b/shortcuts/sheets/flag_defs_gen.go @@ -995,6 +995,14 @@ var flagDefs = map[string]commandDef{ {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, }, }, + "+undo": { + Risk: "write", + Flags: []flagDef{ + {Name: "url", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet locator"}, + {Name: "spreadsheet-token", Kind: "public", Type: "string", Required: "xor", Desc: "Spreadsheet locator"}, + {Name: "dry-run", Kind: "system", Type: "bool", Required: "optional"}, + }, + }, "+workbook-create": { Risk: "write", Flags: []flagDef{ diff --git a/shortcuts/sheets/lark_sheet_undo.go b/shortcuts/sheets/lark_sheet_undo.go new file mode 100644 index 000000000..8327578ed --- /dev/null +++ b/shortcuts/sheets/lark_sheet_undo.go @@ -0,0 +1,47 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import ( + "context" + + "github.com/larksuite/cli/shortcuts/common" +) + +var Undo = common.Shortcut{ + Service: "sheets", + Command: "+undo", + Description: "Undo the current user's latest spreadsheet write.", + Risk: "write", + Scopes: []string{"sheets:spreadsheet:write_only"}, + AuthTypes: []string{"user"}, + HasFormat: true, + Flags: historyLocatorFlags(), + Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { + _, err := resolveSpreadsheetToken(runtime) + return err + }, + DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { + token, _ := resolveSpreadsheetToken(runtime) + return invokeToolDryRun(token, ToolKindWrite, "undo_last", undoInput(token)) + }, + Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { + token, err := resolveSpreadsheetTokenExec(runtime) + if err != nil { + return err + } + out, err := callTool(ctx, runtime, token, ToolKindWrite, "undo_last", undoInput(token)) + if err != nil { + return err + } + runtime.Out(out, nil) + return nil + }, +} + +func undoInput(token string) map[string]interface{} { + return map[string]interface{}{ + "excel_id": token, + } +} diff --git a/shortcuts/sheets/lark_sheet_undo_test.go b/shortcuts/sheets/lark_sheet_undo_test.go new file mode 100644 index 000000000..86ea1fc32 --- /dev/null +++ b/shortcuts/sheets/lark_sheet_undo_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2026 Lark Technologies Pte. Ltd. +// SPDX-License-Identifier: MIT + +package sheets + +import "testing" + +func TestUndo_DryRun(t *testing.T) { + t.Parallel() + + args := []string{"--url", testURL, "--as", "user"} + callURL := dryRunFirstCallURL(t, Undo, args) + if !containsSuffix(callURL, "invoke_write") { + t.Errorf("invoke url = %q, want invoke_write", callURL) + } + + body := parseDryRunBody(t, Undo, args) + got := decodeToolInput(t, body, "undo_last") + assertInputEquals(t, got, map[string]interface{}{ + "excel_id": testToken, + }) +} + +func TestExecute_Undo(t *testing.T) { + t.Parallel() + + stub := toolOutputStub(testToken, "write", `{"undone":1,"op_id":"op-1","top_doc_revision":2,"new_revision":3}`) + out, err := runShortcutWithStubs(t, Undo, []string{"--url", testURL, "--as", "user"}, stub) + if err != nil { + t.Fatalf("execute failed: %v\nout=%s", err, out) + } + + body := decodeRawEnvelopeBody(t, stub.CapturedBody) + input := decodeToolInput(t, body, "undo_last") + assertInputEquals(t, input, map[string]interface{}{ + "excel_id": testToken, + }) + + data := decodeEnvelopeData(t, out) + if data["undone"].(float64) != 1 || data["op_id"] != "op-1" { + t.Fatalf("unexpected output data: %#v", data) + } +} diff --git a/shortcuts/sheets/shortcuts.go b/shortcuts/sheets/shortcuts.go index 759371f2c..6b00793a1 100644 --- a/shortcuts/sheets/shortcuts.go +++ b/shortcuts/sheets/shortcuts.go @@ -160,5 +160,6 @@ func shortcutList() []common.Shortcut { HistoryList, HistoryRevert, HistoryRevertStatus, + Undo, } }