mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
ba104380ee
Add two single-page slide shortcuts on top of the raw xml_presentation.slide create/delete APIs. slides +add-slide appends or inserts one page into an existing presentation. It accepts --presentation as a token, a /slides/ URL or a /wiki/ URL (resolved via wiki.spaces.get_node and checked for obj_type=slides), takes the page XML through --slide as a literal, @file or stdin so the document never has to be escaped into JSON and then into the shell, and auto-uploads <img src="@./local.png"> placeholders, replacing them with the returned file_token. Omitting --before-slide-id appends to the end; the field is dropped from the body rather than sent empty, which the backend rejects as an unknown slide. slides +delete-slide removes one page by slide_id with the same --presentation resolution. It is deliberately Risk "write" rather than the raw command's high-risk-write, so it does not require --yes: it targets a single explicit page and the deck keeps its version history. Both take one page at a time so that batching stays an explicit loop and every call has an unambiguous outcome. The image placeholder validation used by +create is extracted into a shared helper so both commands fail before any API call when a referenced file is missing, is not a regular file or exceeds the 20 MB upload limit. Covered by unit tests and by dry-run e2e tests through the built binary, which is the only layer that proves a full <slide> document survives flag parsing intact. Reference docs are added for both commands and the existing slides skill docs now route to them.
64 lines
2.6 KiB
Go
64 lines
2.6 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package slides
|
|
|
|
import (
|
|
"errors"
|
|
"io/fs"
|
|
|
|
"github.com/larksuite/cli/errs"
|
|
"github.com/larksuite/cli/extension/fileio"
|
|
)
|
|
|
|
// slidesInputStatError maps a FileIO.Stat error for an input image path to a
|
|
// typed validation error, prefixing the caller's context message and tagging
|
|
// the offending flag via param so callers route on the typed Param rather than
|
|
// parsing the message. Both path validation failures and other stat errors are
|
|
// user-actionable input problems (exit code 2). Already-typed errors are not
|
|
// expected here (Stat returns raw fs errors), so this always classifies as
|
|
// validation.
|
|
//
|
|
// Why the "file not found" wording lives here and not at the call sites: Stat
|
|
// fails for a missing path, an unreadable parent directory and a rejected path
|
|
// shape alike, so a caller-supplied "file not found" suffix turns a permission
|
|
// error into the self-contradicting "file not found: permission denied". context
|
|
// names what was being read (the flag, the @-placeholder); this helper decides
|
|
// what actually went wrong.
|
|
func slidesInputStatError(err error, param, context string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
switch {
|
|
case errors.Is(err, fileio.ErrPathValidation):
|
|
return errs.NewValidationError(errs.SubtypeInvalidArgument, "%s: unsafe file path: %s", context, err).WithParam(param).WithCause(err)
|
|
case errors.Is(err, fs.ErrNotExist):
|
|
// The raw error only restates the path, which context already names.
|
|
return errs.NewValidationError(errs.SubtypeInvalidArgument, "%s: file not found", context).WithParam(param).WithCause(err)
|
|
default:
|
|
return errs.NewValidationError(errs.SubtypeInvalidArgument, "%s: cannot read file: %s", context, err).WithParam(param).WithCause(err)
|
|
}
|
|
}
|
|
|
|
// appendSlidesProgressHint preserves err's typed classification (per
|
|
// ERROR_CONTRACT.md "propagate typed errors unchanged") and appends an
|
|
// orchestration-progress hint — e.g. "presentation was created; N image(s)
|
|
// uploaded before failure" — so a failure mid-sequence still tells the caller
|
|
// what partial state exists. An unclassified error (e.g. surfaced from a shared
|
|
// helper boundary before it can be classified) falls back to a typed internal
|
|
// error carrying the hint.
|
|
func appendSlidesProgressHint(err error, hint string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
if p, ok := errs.ProblemOf(err); ok {
|
|
if p.Hint != "" {
|
|
p.Hint = p.Hint + "\n" + hint
|
|
} else {
|
|
p.Hint = hint
|
|
}
|
|
return err
|
|
}
|
|
return errs.NewInternalError(errs.SubtypeUnknown, "%s", err.Error()).WithHint(hint).WithCause(err)
|
|
}
|