diff --git a/README.md b/README.md index c38ab95..c742d10 100644 --- a/README.md +++ b/README.md @@ -576,6 +576,9 @@ gitlink-cli search +repos -k "machine learning" # Search users gitlink-cli search +users -k "zhangsan" + +# List recommended / featured projects +gitlink-cli search +recommend ``` ### User Profile diff --git a/README.zh-CN.md b/README.zh-CN.md index b516d3e..a98e618 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -549,6 +549,9 @@ gitlink-cli search +repos -k "machine learning" # 搜索用户 gitlink-cli search +users -k "zhangsan" + +# 列出推荐/精选项目 +gitlink-cli search +recommend ``` ### 用户画像 diff --git a/doc/changes/search-recommend.md b/doc/changes/search-recommend.md new file mode 100644 index 0000000..448b0b3 --- /dev/null +++ b/doc/changes/search-recommend.md @@ -0,0 +1,39 @@ +# Search: Recommended Projects + +## Summary + +Adds `search +recommend`, which lists the platform's recommended / featured +projects. This wraps `GET /api/projects/recommend`, a discovery endpoint that +previously had no shortcut coverage. + +## Command + +| Command | Purpose | Endpoint | +|---------|---------|----------| +| `gitlink-cli search +recommend` | List recommended/featured projects | `GET /projects/recommend` | + +Each item includes `id`, `identifier`, `name`, `visits`, `author` +(`name`/`login`/`image_url`) and `category`. + +## Behaviour + +- Takes no arguments. Honors the global `--format` (json/table/yaml). +- The endpoint returns a bare JSON array; the command normalizes it into + structured data so the output is clean (not an escaped JSON string). + +## Tests + +Unit tests cover the endpoint path, the bare-array normalization, and HTTP +error handling. + +## 中文说明 + +### 变更内容 + +- 新增 `search +recommend`,列出平台推荐/精选项目,封装此前无 shortcut 的 + `GET /api/projects/recommend`。 +- 无需参数,遵循全局 `--format`;对该接口返回的裸 JSON 数组做结构化归一,输出整洁。 + +### 价值 + +为项目发现提供入口(科研选题/技术调研时可快速看到平台精选项目),补全 Raw API 封装。 diff --git a/internal/i18n/locales/en-US.json b/internal/i18n/locales/en-US.json index 75cd866..74c432a 100644 --- a/internal/i18n/locales/en-US.json +++ b/internal/i18n/locales/en-US.json @@ -102,6 +102,8 @@ "cmd.repo.tree.short": "List repository files and directories", "cmd.root.long": "Manage repositories, issues, pull requests, releases, CI and workflows on GitLink.", "cmd.root.short": "GitLink CLI - command-line tool for GitLink", + "cmd.search.recommend.long": "List the platform's recommended/featured projects (id, name, visits, author, category).", + "cmd.search.recommend.short": "List recommended projects", "cmd.search.repos.short": "Search repositories", "cmd.search.short": "Search operations", "cmd.search.users.short": "Search users", diff --git a/internal/i18n/locales/zh-CN.json b/internal/i18n/locales/zh-CN.json index c3d491d..66cf8ab 100644 --- a/internal/i18n/locales/zh-CN.json +++ b/internal/i18n/locales/zh-CN.json @@ -102,6 +102,8 @@ "cmd.repo.tree.short": "列出仓库文件和目录", "cmd.root.long": "用于管理 GitLink 上的仓库、议题、拉取请求、发布、CI 和工作流。", "cmd.root.short": "GitLink CLI - GitLink 命令行工具", + "cmd.search.recommend.long": "列出平台推荐/精选项目(ID、名称、访问量、作者、分类)。", + "cmd.search.recommend.short": "列出推荐项目", "cmd.search.repos.short": "搜索仓库", "cmd.search.short": "搜索操作", "cmd.search.users.short": "搜索用户", diff --git a/shortcuts/search/search.go b/shortcuts/search/search.go index a0ee4c2..a9e9036 100644 --- a/shortcuts/search/search.go +++ b/shortcuts/search/search.go @@ -1,6 +1,7 @@ package search import ( + "encoding/json" "net/url" "github.com/gitlink-org/gitlink-cli/internal/i18n" @@ -52,6 +53,26 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut { return ctx.Output(env) }, }, + { + Name: "recommend", + Description: tr.T("cmd.search.recommend.short"), + Long: tr.T("cmd.search.recommend.long"), + Run: func(ctx *common.RuntimeContext) error { + env, err := ctx.CallAPI("GET", "/projects/recommend", nil) + if err != nil { + return err + } + // This endpoint returns a bare JSON array, which the client + // surfaces as a raw string; normalize it to structured data. + if s, ok := env.Data.(string); ok { + var arr interface{} + if json.Unmarshal([]byte(s), &arr) == nil { + return ctx.OutputData(arr) + } + } + return ctx.Output(env) + }, + }, } } diff --git a/shortcuts/search/search_test.go b/shortcuts/search/search_test.go index 8578f6f..096cce0 100644 --- a/shortcuts/search/search_test.go +++ b/shortcuts/search/search_test.go @@ -83,6 +83,37 @@ func TestSearchUsers(t *testing.T) { } } +// --- recommend --- + +func TestSearchRecommend(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/projects/recommend.json" { + t.Fatalf("unexpected path: %s", r.URL.Path) + } + // bare JSON array (matches the real endpoint shape) + writeJSON(w, []interface{}{ + map[string]interface{}{"identifier": "forgeplus", "name": "确实开源", "visits": float64(48497)}, + }) + })) + defer server.Close() + + if err := runShortcut(t, server, "recommend", nil); err != nil { + t.Fatalf("recommend failed: %v", err) + } +} + +func TestSearchRecommendHTTPError(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("server error")) + })) + defer server.Close() + + if err := runShortcut(t, server, "recommend", nil); err == nil { + t.Fatal("expected error for HTTP 500") + } +} + // --- HTTP error paths --- func TestSearchReposHTTPError(t *testing.T) {