Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions shortcuts/doc/docs_create_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
return []common.Flag{
{Name: "title", Desc: "document title; when provided, the CLI prepends it to --content as <title>...</title> so the title wins over later content titles"},
{Name: "content", Desc: "document body; XML by default or Markdown when --doc-format markdown. " + docsContentSkillHelp + "; use --help for the latest command flags", Input: []string{common.File, common.Stdin}},
{Name: "reference-map", Desc: docsReferenceMapFlagDesc, Input: []string{common.File, common.Stdin}},
{Name: "doc-format", Desc: "content format; xml is default and supports richer DocxXML blocks, markdown imports plain Markdown", Default: "xml", Enum: []string{"xml", "markdown"}},
{Name: "parent-token", Desc: "parent folder token or wiki node token; mutually exclusive with --parent-position"},
{Name: "parent-position", Desc: "parent position such as my_library; mutually exclusive with --parent-token"},
Expand All @@ -32,20 +33,30 @@
if runtime.Changed("title") && title == "" {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--title must not be empty").WithParam("--title")
}
if runtime.Str("content") == "" && title == "" {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content is required unless --title is provided").WithParam("--content")
if err := validateDocsV2ReferenceMapFlags(runtime); err != nil {
return err

Check warning on line 37 in shortcuts/doc/docs_create_v2.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/docs_create_v2.go#L37

Added line #L37 was not covered by tests
}
if runtime.Str("parent-token") != "" && runtime.Str("parent-position") != "" {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--parent-token and --parent-position are mutually exclusive").WithParams(
errs.InvalidParam{Name: "--parent-token", Reason: "mutually exclusive with --parent-position"},
errs.InvalidParam{Name: "--parent-position", Reason: "mutually exclusive with --parent-token"},
)
}
if runtime.Str("content") == "" && title == "" {
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--content is required unless --title is provided").WithParam("--content")
}
if runtime.Str("content") != "" {
_, err := resolveDocsV2ContentReferenceMap(runtime)
return err
}
return nil
}

func dryRunCreateV2(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
body := buildCreateBody(runtime)
body, err := buildCreateBodyWithHTML5ReferenceMap(runtime)
if err != nil {
return common.NewDryRunAPI().Set("error", err.Error())

Check warning on line 58 in shortcuts/doc/docs_create_v2.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/docs_create_v2.go#L56-L58

Added lines #L56 - L58 were not covered by tests
}
desc := "OpenAPI: create document"
if runtime.IsBot() {
desc += ". After document creation succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new document."
Expand All @@ -57,7 +68,10 @@
}

func executeCreateV2(_ context.Context, runtime *common.RuntimeContext) error {
body := buildCreateBody(runtime)
body, err := buildCreateBodyWithHTML5ReferenceMap(runtime)
if err != nil {
return err

Check warning on line 73 in shortcuts/doc/docs_create_v2.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/docs_create_v2.go#L73

Added line #L73 was not covered by tests
}

data, err := doDocAPI(runtime, "POST", "/open-apis/docs_ai/v1/documents", body)
if err != nil {
Expand Down Expand Up @@ -86,7 +100,10 @@
}

func buildCreateContent(runtime *common.RuntimeContext) string {
content := runtime.Str("content")
return buildCreateContentWithBody(runtime, runtime.Str("content"))
}

func buildCreateContentWithBody(runtime *common.RuntimeContext, content string) string {
title := strings.TrimSpace(runtime.Str("title"))
if title == "" {
return content
Expand Down
5 changes: 4 additions & 1 deletion shortcuts/doc/docs_fetch_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"github.com/larksuite/cli/shortcuts/common"
)

const docsFetchExtraParam = `{"enable_user_cite_reference_map":true}`
const docsFetchExtraParam = `{"enable_user_cite_reference_map":true,"return_html5_block_data":true}`

// v2FetchFlags returns the flag definitions for the v2 (OpenAPI) fetch path.
func v2FetchFlags() []common.Flag {
Expand Down Expand Up @@ -71,6 +71,9 @@ func executeFetchV2(_ context.Context, runtime *common.RuntimeContext) error {
if err != nil {
return err
}
if err := processHTML5BlockReferenceMapForFetch(runtime, effectiveFetchFormat(runtime), ref.Token, data); err != nil {
return err
}
if warning := addFetchDetailDowngradeWarning(runtime, data); warning != "" && runtime.Format == "pretty" {
fmt.Fprintf(runtime.IO().ErrOut, "warning: %s\n", warning)
}
Expand Down
48 changes: 44 additions & 4 deletions shortcuts/doc/docs_fetch_v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,14 +505,14 @@ func TestBuildFetchBodyIncludesFetchExtraParamByDefault(t *testing.T) {
if got["enable_user_cite_reference_map"] != true {
t.Fatalf("enable_user_cite_reference_map = %#v, want true in %#v", got["enable_user_cite_reference_map"], got)
}
if _, ok := got["return_html5_block_data"]; ok {
t.Fatalf("extra_param should not request html5 block data: %#v", got)
if got["return_html5_block_data"] != true {
t.Fatalf("return_html5_block_data = %#v, want true in %#v", got["return_html5_block_data"], got)
}
if _, ok := got["reference_map_mode"]; ok {
t.Fatalf("extra_param should not use legacy reference_map_mode: %#v", got)
}
if len(got) != 1 {
t.Fatalf("extra_param should only contain fetch reference_map toggle: %#v", got)
if len(got) != 2 {
t.Fatalf("extra_param should only contain fetch reference_map and html5 data toggles: %#v", got)
}
}

Expand Down Expand Up @@ -579,6 +579,46 @@ func TestDocsFetchIMMarkdownRequestsMarkdownFromAPI(t *testing.T) {
}
}

func TestDocsFetchIMMarkdownIgnoresHTML5BlockInsideCodeFence(t *testing.T) {
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())

f, stdout, _, reg := cmdutil.TestFactory(t, docsTestConfigWithAppID("docs-fetch-im-markdown-code-fence"))
registerDocsAIStub(reg, "POST", "/open-apis/docs_ai/v1/documents/doxcnFetchIMMarkdownFence/fetch", map[string]interface{}{
"document": map[string]interface{}{
"document_id": "doxcnFetchIMMarkdownFence",
"revision_id": float64(1),
"content": "```xml\n<html5-block data-ref=\"html5_1\"></html5-block>\n```\n",
},
})

err := mountAndRunDocs(t, DocsFetch, []string{
"+fetch",
"--doc", "doxcnFetchIMMarkdownFence",
"--doc-format", "im-markdown",
"--format", "json",
"--as", "bot",
}, f, stdout)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
var envelope map[string]interface{}
if err := json.Unmarshal(stdout.Bytes(), &envelope); err != nil {
t.Fatalf("decode output: %v\nraw=%s", err, stdout.String())
}
if errField, ok := envelope["error"]; ok {
t.Fatalf("fetch output should not contain error: %#v", errField)
}
data, _ := envelope["data"].(map[string]interface{})
doc, _ := data["document"].(map[string]interface{})
content, _ := doc["content"].(string)
if !strings.Contains(content, "```xml\n<html5-block data-ref=\"html5_1\"></html5-block>\n```") {
t.Fatalf("fenced html5-block should stay in content, got:\n%s", content)
}
if _, ok := doc["reference_map"]; ok {
t.Fatalf("fenced html5-block should not create reference_map side effects: %#v", doc["reference_map"])
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved

func TestDocsFetchMarkdownDetailDowngradesToSimple(t *testing.T) {
t.Parallel()

Expand Down
34 changes: 34 additions & 0 deletions shortcuts/doc/docs_update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package doc
import (
"context"
"errors"
"os"
"strings"
"testing"

Expand Down Expand Up @@ -63,6 +64,39 @@ func TestDocsUpdateDryRunIgnoresAPIVersionCompatFlag(t *testing.T) {
}
}

func TestBuildUpdateBodyWithHTML5ReferenceMapReportsPathError(t *testing.T) {
t.Parallel()

runtime := newUpdateShortcutTestRuntime(t, "", map[string]string{
"content": `<html5-block path="@missing.html"></html5-block>`,
})

_, err := buildUpdateBodyWithHTML5ReferenceMap(runtime)
if err == nil {
t.Fatal("buildUpdateBodyWithHTML5ReferenceMap() succeeded, want error")
}
p, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("ProblemOf() ok = false for %T (%v)", err, err)
}
if p.Category != errs.CategoryValidation {
t.Fatalf("category = %q, want %q", p.Category, errs.CategoryValidation)
}
if p.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("subtype = %q, want %q", p.Subtype, errs.SubtypeInvalidArgument)
}
var validationErr *errs.ValidationError
if !errors.As(err, &validationErr) {
t.Fatalf("error type = %T, want *errs.ValidationError", err)
}
if validationErr.Param != "path" {
t.Fatalf("param = %q, want path", validationErr.Param)
}
if !errors.Is(err, os.ErrNotExist) {
t.Fatalf("error should preserve os.ErrNotExist cause, got: %v", err)
}
}

func TestDocsUpdateV2ReferenceMapFlagIsPublicFileInput(t *testing.T) {
t.Parallel()

Expand Down
15 changes: 12 additions & 3 deletions shortcuts/doc/docs_update_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
"append": true,
}

const docsUpdateReferenceMapFlagDesc = "结构化 `reference_map` JSON object;当 `--content` 使用正文外部载荷 / 引用映射时与内容一起传给服务,支持直接 JSON、`@reference-map.json`(相对路径)或 `-` 从 stdin 读取。通常用于回写已有 `document.reference_map`。"
const docsReferenceMapFlagDesc = "结构化 `reference_map` JSON object;必须与 `--content` 一起使用。普通写入优先把结构写在正文里;`--reference-map` 主要用于保留或回放已有 `document.reference_map`。支持直接 JSON、`@reference-map.json`(相对路径)或 `-` 从 stdin 读取。"

const docsUpdateReferenceMapFlagDesc = docsReferenceMapFlagDesc

// v2UpdateFlags returns the flag definitions for the v2 (OpenAPI) update path.
func v2UpdateFlags() []common.Flag {
Expand Down Expand Up @@ -115,13 +117,20 @@
return errs.NewValidationError(errs.SubtypeInvalidArgument, "--command append requires --content").WithParam("--content")
}
}
Comment thread
fangshuyu-768 marked this conversation as resolved.
Resolved
if content != "" {
_, err := resolveDocsV2ContentReferenceMap(runtime)
return err
}
return nil
}

func dryRunUpdateV2(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
// Validate has already accepted --doc; parseDocumentRef cannot fail here.
ref, _ := parseDocumentRef(runtime.Str("doc"))
body, _ := buildUpdateBodyWithReferenceMap(runtime)
body, err := buildUpdateBodyWithHTML5ReferenceMap(runtime)
if err != nil {
return common.NewDryRunAPI().Set("error", err.Error())

Check warning on line 132 in shortcuts/doc/docs_update_v2.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/doc/docs_update_v2.go#L132

Added line #L132 was not covered by tests
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
apiPath := fmt.Sprintf("/open-apis/docs_ai/v1/documents/%s", ref.Token)
return common.NewDryRunAPI().
PUT(apiPath).
Expand All @@ -134,7 +143,7 @@
ref, _ := parseDocumentRef(runtime.Str("doc"))

apiPath := fmt.Sprintf("/open-apis/docs_ai/v1/documents/%s", ref.Token)
body, err := buildUpdateBodyWithReferenceMap(runtime)
body, err := buildUpdateBodyWithHTML5ReferenceMap(runtime)
if err != nil {
return err
}
Expand Down
Loading
Loading