mirror of
https://github.com/larksuite/cli.git
synced 2026-07-09 02:14:02 +08:00
Add 8 new shortcut commands under `lark-cli apps`: Plugin package management (aligned with fullstack-cli): - +plugin-install: download tgz, extract to node_modules, update package.json - +plugin-uninstall: remove from node_modules and package.json actionPlugins - +plugin-list: list declared plugins with installation status Plugin instance CRUD (aligned with feida-ai): - +plugin-instance-create: validate + write capability JSON with formValue validation - +plugin-instance-update: merge mutable fields, re-validate formValue - +plugin-instance-delete: idempotent file removal - +plugin-instance-get: read capability JSON - +plugin-instance-list: scan capabilities directory Shared infrastructure (plugin_common.go): - 4-level capabilities dir resolution (flag → env → .env.local MIAODA_APP_TYPE → detection) - formValue validation ported from feida-ai (5 rules: forbidden Handlebars, paramsSchema type constraints, input ref existence, unconsumed params, array double-wrap auto-fix) - tgz extraction with path traversal protection - package.json actionPlugins management - Install version check with mismatch warnings
107 lines
3.0 KiB
Go
107 lines
3.0 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package apps
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestPluginList_Empty(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeTestPkgJSON(t, dir, map[string]interface{}{})
|
|
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
err := runAppsShortcut(t, AppsPluginList, []string{
|
|
"+plugin-list", "--project-path", dir, "--format", "json", "--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var env map[string]interface{}
|
|
json.Unmarshal(stdout.Bytes(), &env)
|
|
data, _ := env["data"].(map[string]interface{})
|
|
plugins, _ := data["plugins"].([]interface{})
|
|
if len(plugins) != 0 {
|
|
t.Errorf("expected 0 plugins, got %d", len(plugins))
|
|
}
|
|
}
|
|
|
|
func TestPluginList_Installed(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeTestPkgJSON(t, dir, map[string]interface{}{
|
|
"actionPlugins": map[string]interface{}{
|
|
"@test/my-plugin": "1.0.0",
|
|
},
|
|
})
|
|
manifestDir := filepath.Join(dir, "node_modules", "@test/my-plugin")
|
|
os.MkdirAll(manifestDir, 0o755) //nolint:forbidigo
|
|
os.WriteFile(filepath.Join(manifestDir, "package.json"), []byte(`{"version":"1.0.0"}`), 0o644) //nolint:forbidigo
|
|
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
err := runAppsShortcut(t, AppsPluginList, []string{
|
|
"+plugin-list", "--project-path", dir, "--format", "json", "--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var env map[string]interface{}
|
|
json.Unmarshal(stdout.Bytes(), &env)
|
|
data, _ := env["data"].(map[string]interface{})
|
|
plugins, _ := data["plugins"].([]interface{})
|
|
if len(plugins) != 1 {
|
|
t.Fatalf("expected 1 plugin, got %d", len(plugins))
|
|
}
|
|
p := plugins[0].(map[string]interface{})
|
|
if p["status"] != "installed" {
|
|
t.Errorf("status = %v, want installed", p["status"])
|
|
}
|
|
}
|
|
|
|
func TestPluginList_DeclaredNotInstalled(t *testing.T) {
|
|
dir := t.TempDir()
|
|
writeTestPkgJSON(t, dir, map[string]interface{}{
|
|
"actionPlugins": map[string]interface{}{
|
|
"@test/missing": "1.0.0",
|
|
},
|
|
})
|
|
|
|
factory, stdout, _ := newAppsExecuteFactory(t)
|
|
err := runAppsShortcut(t, AppsPluginList, []string{
|
|
"+plugin-list", "--project-path", dir, "--format", "json", "--as", "user",
|
|
}, factory, stdout)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
var env map[string]interface{}
|
|
json.Unmarshal(stdout.Bytes(), &env)
|
|
data, _ := env["data"].(map[string]interface{})
|
|
plugins, _ := data["plugins"].([]interface{})
|
|
if len(plugins) != 1 {
|
|
t.Fatalf("expected 1 plugin, got %d", len(plugins))
|
|
}
|
|
p := plugins[0].(map[string]interface{})
|
|
if p["status"] != "declared_not_installed" {
|
|
t.Errorf("status = %v, want declared_not_installed", p["status"])
|
|
}
|
|
}
|
|
|
|
// --- helpers ---
|
|
|
|
func writeTestPkgJSON(t *testing.T, dir string, pkg map[string]interface{}) {
|
|
t.Helper()
|
|
data, err := json.Marshal(pkg)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "package.json"), data, 0o644); err != nil { //nolint:forbidigo
|
|
t.Fatal(err)
|
|
}
|
|
}
|