diff --git a/cmd/auth/auth.go b/cmd/auth/auth.go index ed9dc22..13437de 100644 --- a/cmd/auth/auth.go +++ b/cmd/auth/auth.go @@ -44,19 +44,25 @@ func newLoginCmd() *cobra.Command { } func loginWithPassword() error { - reader := bufio.NewReader(os.Stdin) + // Check for credentials from environment variables for non-interactive login + username := os.Getenv("GITLINK_USERNAME") + password := os.Getenv("GITLINK_PASSWORD") - fmt.Print("Username/Email/Phone: ") - username, _ := reader.ReadString('\n') - username = strings.TrimSpace(username) + if username == "" || password == "" { + reader := bufio.NewReader(os.Stdin) - fmt.Print("Password: ") - passwordBytes, err := term.ReadPassword(int(syscall.Stdin)) - if err != nil { - return fmt.Errorf("failed to read password: %w", err) + fmt.Print("Username/Email/Phone: ") + usernameInput, _ := reader.ReadString('\n') + username = strings.TrimSpace(usernameInput) + + fmt.Print("Password: ") + passwordBytes, err := term.ReadPassword(int(syscall.Stdin)) + if err != nil { + return fmt.Errorf("failed to read password: %w", err) + } + fmt.Println() + password = string(passwordBytes) } - fmt.Println() - password := string(passwordBytes) result, err := internalAuth.Login(username, password) if err != nil { diff --git a/gitlink-cli.exe b/gitlink-cli.exe index 958a8da..efff36f 100644 Binary files a/gitlink-cli.exe and b/gitlink-cli.exe differ diff --git a/internal/auth/login.go b/internal/auth/login.go index 4b035e7..032f107 100644 --- a/internal/auth/login.go +++ b/internal/auth/login.go @@ -71,7 +71,7 @@ func Login(username, password string) (*LoginResult, error) { // Collect auth cookies from response (GitLink uses autologin_trustie for session persistence) var authCookies []string for _, cookie := range resp.Cookies() { - if cookie.Name == "autologin_trustie" || cookie.Name == "_educoder_session" { + if cookie.Name == "Authorization" || cookie.Name == "autologin_trustie" || cookie.Name == "_educoder_session" || cookie.Name == "autologin" || cookie.Name == "login" { authCookies = append(authCookies, cookie.Name+"="+cookie.Value) } } @@ -79,7 +79,7 @@ func Login(username, password string) (*LoginResult, error) { if len(authCookies) == 0 { if u, err := url.Parse(loginURL); err == nil { for _, cookie := range jar.Cookies(u) { - if cookie.Name == "autologin_trustie" || cookie.Name == "_educoder_session" { + if cookie.Name == "Authorization" || cookie.Name == "autologin_trustie" || cookie.Name == "_educoder_session" || cookie.Name == "autologin" || cookie.Name == "login" { authCookies = append(authCookies, cookie.Name+"="+cookie.Value) } } diff --git a/internal/context/repo.go b/internal/context/repo.go index f3a7085..0c9adfb 100644 --- a/internal/context/repo.go +++ b/internal/context/repo.go @@ -59,11 +59,33 @@ func parseRemoteURL(remote string) (string, string, error) { } func parsePathSegments(path string) (string, string, error) { - path = strings.TrimPrefix(path, "/") + // 处理URL路径格式:可能包含域名前缀 + // 例如://www.gitlink.org.cn/zzx-coder/gitlink-cli 或 /zzx-coder/gitlink-cli + + // 先去掉域名部分(如果存在) + if strings.HasPrefix(path, "//www.gitlink.org.cn/") { + path = strings.TrimPrefix(path, "//www.gitlink.org.cn/") + } else if strings.HasPrefix(path, "//") { + // 处理其他可能的域名格式:找到第二个斜杠后的内容 + if idx := strings.Index(path[2:], "/"); idx != -1 { + path = path[2+idx+1:] + } else { + path = path[2:] + } + } else if strings.HasPrefix(path, "/") { + // 去掉单个前导斜杠 + path = strings.TrimPrefix(path, "/") + } + + // 去掉.git后缀 path = strings.TrimSuffix(path, ".git") - parts := strings.SplitN(path, "/", 3) + + // 现在应该得到 "zzx-coder/gitlink-cli" 格式 + parts := strings.Split(path, "/") if len(parts) < 2 { return "", "", fmt.Errorf("cannot extract owner/repo from path: %s", path) } + + // 第一个部分是owner,第二个是repo(可能还有更多部分但忽略) return parts[0], parts[1], nil } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..28e1fcb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "gitlink-cli", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/shortcuts/webhook/webhook.go b/shortcuts/webhook/webhook.go index 5bd1acb..ebeb5e2 100644 --- a/shortcuts/webhook/webhook.go +++ b/shortcuts/webhook/webhook.go @@ -48,6 +48,12 @@ func parseEvents(eventsStr string) []string { return validEvents } +// webhookRepoPath returns the webhook API path prefix: /v1/{owner}/{repo} +// Note: BaseURL already includes /api prefix +func webhookRepoPath(ctx *common.RuntimeContext) string { + return fmt.Sprintf("/v1/%s/%s", ctx.Owner, ctx.Repo) +} + func Shortcuts() []*common.Shortcut { return []*common.Shortcut{ { @@ -64,7 +70,7 @@ func Shortcuts() []*common.Shortcut { q := url.Values{} q.Set("page", ctx.Arg("page")) q.Set("limit", ctx.Arg("limit")) - env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/hooks", q) + env, err := ctx.CallAPIWithQuery("GET", webhookRepoPath(ctx)+"/webhooks", q) if err != nil { return err } @@ -78,7 +84,6 @@ func Shortcuts() []*common.Shortcut { {Name: "url", Short: "u", Usage: "Webhook callback URL", Required: true}, {Name: "events", Short: "e", Usage: "Trigger events (comma-separated), e.g., push,pull_request,issue", Default: "push"}, {Name: "active", Usage: "Webhook active status (true/false)", Default: "true"}, - {Name: "content_type", Usage: "Content type (json/form)", Default: "json"}, {Name: "secret", Usage: "Webhook secret for HMAC verification"}, {Name: "description", Short: "d", Usage: "Webhook description"}, }, @@ -98,10 +103,16 @@ func Shortcuts() []*common.Shortcut { } payload := map[string]interface{}{ - "hook_url": webhookURL, - "events": events, - "is_active": ctx.Arg("active") == "true", - "content_type": ctx.Arg("content_type"), + "url": webhookURL, + "http_method": "POST", + "active": true, + "content_type": "json", + } + + if len(events) > 0 { + payload["events"] = events + } else { + payload["events"] = []string{"push"} } if secret := ctx.Arg("secret"); secret != "" { @@ -112,7 +123,7 @@ func Shortcuts() []*common.Shortcut { payload["description"] = description } - env, err := ctx.CallAPI("POST", ctx.RepoPath()+"/hooks", payload) + env, err := ctx.CallAPI("POST", webhookRepoPath(ctx)+"/webhooks", payload) if err != nil { return err } @@ -141,12 +152,31 @@ func Shortcuts() []*common.Shortcut { return err } - payload := map[string]interface{}{} - - if webhookURL := ctx.Arg("url"); webhookURL != "" { - payload["hook_url"] = webhookURL + payload := map[string]interface{}{ + "http_method": "POST", + "active": true, + "content_type": "json", } + // 如果用户没有提供URL,获取当前webhook的URL + webhookURL := ctx.Arg("url") + if webhookURL == "" { + getEnv, err := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), nil) + if err != nil { + return fmt.Errorf("failed to get current webhook info: %w", err) + } + webhookData, ok := getEnv.Data.(map[string]interface{}) + if !ok { + return fmt.Errorf("failed to parse webhook data") + } + currentURL, ok := webhookData["url"].(string) + if !ok || currentURL == "" { + return fmt.Errorf("failed to get current webhook URL") + } + webhookURL = currentURL + } + payload["url"] = webhookURL + if events := ctx.Arg("events"); events != "" { validEvents := parseEvents(events) if len(validEvents) == 0 { @@ -155,9 +185,6 @@ func Shortcuts() []*common.Shortcut { payload["events"] = validEvents } - if active := ctx.Arg("active"); active != "" { - payload["is_active"] = active == "true" - } if contentType := ctx.Arg("content_type"); contentType != "" { payload["content_type"] = contentType @@ -171,11 +198,8 @@ func Shortcuts() []*common.Shortcut { payload["description"] = description } - if len(payload) == 0 { - return fmt.Errorf("no fields specified for update") - } - env, err := ctx.CallAPI("PUT", fmt.Sprintf("%s/hooks/%s", ctx.RepoPath(), webhookID), payload) + env, err := ctx.CallAPI("PUT", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), payload) if err != nil { return err } @@ -198,10 +222,10 @@ func Shortcuts() []*common.Shortcut { return err } - _, delErr := ctx.CallAPI("DELETE", fmt.Sprintf("%s/hooks/%s", ctx.RepoPath(), webhookID), nil) + _, delErr := ctx.CallAPI("DELETE", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), nil) if delErr != nil { // 验证是否真的删除成功(类似release的处理) - _, viewErr := ctx.CallAPI("GET", fmt.Sprintf("%s/hooks/%s", ctx.RepoPath(), webhookID), nil) + _, viewErr := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), nil) if viewErr != nil { // Webhook不存在了,说明删除成功 return ctx.Output(output.SuccessEnvelope(map[string]interface{}{ @@ -237,11 +261,7 @@ func Shortcuts() []*common.Shortcut { return fmt.Errorf("unsupported event type: %s. Supported events: %s", eventType, strings.Join(supportedEvents, ", ")) } - payload := map[string]interface{}{ - "event_type": eventType, - } - - env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/hooks/%s/test", ctx.RepoPath(), webhookID), payload) + env, err := ctx.CallAPI("POST", fmt.Sprintf("%s/webhooks/%s/tests", webhookRepoPath(ctx), webhookID), nil) if err != nil { return err } @@ -264,7 +284,7 @@ func Shortcuts() []*common.Shortcut { return err } - env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/hooks/%s", ctx.RepoPath(), webhookID), nil) + env, err := ctx.CallAPI("GET", fmt.Sprintf("%s/webhooks/%s", webhookRepoPath(ctx), webhookID), nil) if err != nil { return err }