Files
larksuite__cli/shortcuts/apps/shortcuts.go
木杉 4488da0b14 feat(apps): add +user-id-convert shortcut for Miaoda↔Feishu ID conversion (#2270)
* feat(apps): add +user-id-convert shortcut for Miaoda↔Feishu ID conversion

Wrap the platform id_convert OpenAPI as a read-only shortcut that maps
Miaoda user_id ↔ Feishu open platform IDs (open_id / union_id / Feishu
user_id). It does one thing — conversion — with no local mapping table,
caching, permission pre-check, or direction guessing.

- --convert-type enum → server id_convert_type (10/11/20/21/40)
- --ids: csv / @file / stdin, 1-100 per call, not de-duped, input order
- reconstructs data.missed by diffing input positions against returned
  source_ids (server silently drops unresolved IDs), keyed by 0-based index
- meta counters (total/hit_count/missed_count) via pointer fields on
  output.Meta so an explicit missed_count: 0 survives omitempty

* test(apps): address review feedback on +user-id-convert

- reject empty --ids CSV entries (e.g. "a,,b") with a typed validation
  error instead of silently dropping them, since a dropped entry shifts
  every later result's 0-based index and breaks the position-keyed
  items/missed contract; add an interior-empty-element test
- reuse common.GetSlice / common.GetString for response projection
  (house convention) instead of local asSlice/asString helpers
- requireConvertValidation now asserts CategoryValidation +
  SubtypeInvalidArgument via errs.ProblemOf, keeping ValidationError.Param
- table-drive TestResolveConvertType over all five directions so every
  --convert-type → id_convert_type mapping (10/11/20/21/40) is protected

* fix(apps): split newline-delimited --ids for +user-id-convert @file/stdin

@file and - (stdin) input arrives verbatim from the framework as
one-ID-per-line text, but parseConvertIDs only split on commas, so such a
block was sent as a single malformed request ID. Treat a newline as
equivalent to a comma, tolerating a file's trailing newline while still
rejecting interior empty entries so position-keyed result indices stay
aligned. Add @file and stdin tests asserting the request body's ids are
split into discrete IDs.

* fix(apps): stringify numeric JSON IDs in +user-id-convert results

Responses decode with json.Number (client.ParseJSONResponse uses
dec.UseNumber()), so a server that emits source_id/target_id as bare
numbers — plausible for the numeric Miaoda user_id form — was silently
coerced to "" by buildConvertResult's strict string assertion: the
source_id got dropped (false not_found) and the target_id blanked
(false success).

Add common.GetStringLoose, which stringifies string/json.Number/int64/
float64 via literal text (large integer IDs keep full precision, never
routed through a lossy float64), and use it for both id reads. Cover it
with a package-level table test plus an end-to-end regression asserting a
numeric-JSON response yields intact, non-blank ids and no false miss.

Also exercise resolveConvertType's non-empty "not a valid direction"
branch directly, since the runner's enum gate preempts it in normal flow.

* test(common): tighten GetStringLoose numeric coverage

Add an int-branch case (was only covering int64) and swap the float64
fixture from 42 — which no formatter would render in exponent form — to
1e-7, whose fixed-point rendering "0.0000001" fails under the 'g' verb.
This turns the "no scientific notation" case into a real guard for the
'f' verb choice, per CodeRabbit review on c64cca39.
2026-08-11 19:14:52 +08:00

119 lines
3.2 KiB
Go

// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package apps
import "github.com/larksuite/cli/shortcuts/common"
// Shortcuts returns all apps domain shortcuts.
func Shortcuts() []common.Shortcut {
envSet := withExtraTips(AppsEnvVarSet, "Example: lark-cli apps +env-set --app-id <app_id> --environment online --key FOO --value <value> --yes")
envDelete := withExtraTips(AppsEnvVarDelete, "Tip: +env-delete is high-risk-write; only pass --yes after explicit confirmation.")
return []common.Shortcut{
AppsCreate,
AppsGet,
AppsUpdate,
AppsList,
AppsAccessScopeSet,
AppsAccessScopeGet,
AppsRoleList,
AppsRoleGet,
AppsRoleCreate,
AppsRoleUpdate,
AppsRoleDelete,
AppsRoleMemberList,
AppsRoleMemberAdd,
AppsRoleMemberRemove,
AppsRoleMatchList,
AppsMemberList,
AppsMemberAdd,
AppsMemberUpdate,
AppsMemberRemove,
AppsMemberSettingsGet,
AppsMemberSettingsSet,
AppsHTMLPublish,
AppsInit,
AppsReleaseCreate,
AppsReleaseList,
AppsReleaseGet,
AppsEnvPull,
withExtraTips(AppsLogList, "Tip: logs are online-only; keep --environment omitted or set --environment online."),
withExtraTips(AppsLogGet, "Tip: logs are online-only; keep --environment omitted or set --environment online."),
withExtraTips(AppsTraceList, "Tip: traces are online-only; keep --environment omitted or set --environment online."),
withExtraTips(AppsTraceGet, "Tip: traces are online-only; keep --environment omitted or set --environment online."),
withExtraTips(AppsMetricList, "Tip: metrics are online-only; keep --environment omitted or set --environment online."),
withExtraTips(AppsAnalyticsList, "Tip: analytics are online-only; keep --environment omitted or set --environment online."),
AppsEnvVarList,
envSet,
envDelete,
AppsDBTableList,
AppsDBTableGet,
AppsDBExecute,
AppsDBEnvCreate,
AppsDBDataImport,
AppsDBDataExport,
AppsDBSyncCreate,
AppsDBSyncList,
AppsDBSyncGet,
AppsDBSyncEnable,
AppsDBSyncDisable,
AppsDBSyncUpdate,
AppsDBSyncDelete,
AppsDBChangelogList,
AppsDBAuditStatus,
AppsDBAuditEnable,
AppsDBAuditDisable,
AppsDBAuditList,
AppsDBEnvDiff,
AppsDBEnvMigrate,
AppsDBRecoveryDiff,
AppsDBRecoveryApply,
AppsDBQuotaGet,
AppsFileList,
AppsFileGet,
AppsFileSign,
AppsFileDownload,
AppsFileUpload,
AppsFileDelete,
AppsFileQuotaGet,
AppsCacheGet,
AppsCacheDelete,
AppsCacheClear,
AppsGitCredentialInit,
AppsGitCredentialList,
AppsGitCredentialRemove,
AppsSessionCreate,
AppsSessionList,
AppsSessionGet,
AppsSessionStop,
AppsSessionMessagesList,
AppsChat,
AppsUserIDConvert,
AppsPluginInstall,
AppsPluginUninstall,
AppsPluginList,
// open API key management
AppsOpenAPIKeyList,
AppsOpenAPIKeyGet,
AppsOpenAPIKeyCreate,
AppsOpenAPIKeyUpdate,
AppsOpenAPIKeyEnable,
AppsOpenAPIKeyDisable,
AppsOpenAPIKeyDelete,
AppsOpenAPIKeyReset,
// automation triggers (cron / record-change / webhook / feishu-approval)
AppsAutomationList,
AppsAutomationGet,
AppsAutomationCreate,
AppsAutomationUpdate,
AppsAutomationEnable,
AppsAutomationDisable,
}
}
func withExtraTips(sc common.Shortcut, tips ...string) common.Shortcut {
sc.Tips = append(append([]string{}, sc.Tips...), tips...)
return sc
}