Merge PR #347: feat(i18n): 10 个命令组接入 i18n 全覆盖(145 个中英键)
# Conflicts: # shortcuts/compare/compare.go # shortcuts/health/health.go # shortcuts/label/label.go # shortcuts/milestone/milestone.go # shortcuts/register.go # shortcuts/wiki/wiki.go # shortcuts/workflow/workflow.go
This commit is contained in:
commit
cb01801f39
|
|
@ -23,6 +23,9 @@
|
|||
"cmd.ci.restart.short": "Restart a build",
|
||||
"cmd.ci.short": "CI/CD operations",
|
||||
"cmd.ci.stop.short": "Stop a build",
|
||||
"cmd.compare.files.short": "List changed files between two refs",
|
||||
"cmd.compare.short": "Compare branches, tags, or commits",
|
||||
"cmd.compare.view.short": "Compare two branches, tags, or commits",
|
||||
"cmd.config.get.short": "Get a configuration value",
|
||||
"cmd.config.init.short": "Initialize configuration file",
|
||||
"cmd.config.list.short": "List all configuration values",
|
||||
|
|
@ -41,6 +44,10 @@
|
|||
"cmd.dataset.view.short": "View a repository's dataset",
|
||||
"cmd.doctor.long": "Run local diagnostics for gitlink-cli configuration, authentication, repository context and API connectivity.",
|
||||
"cmd.doctor.short": "Diagnose gitlink-cli environment problems",
|
||||
"cmd.health.fetch.short": "Fetch PR and Issue data into SQLite for health analysis",
|
||||
"cmd.health.short": "Project health data collection",
|
||||
"cmd.ignore.list.short": "List available ignore-file templates",
|
||||
"cmd.ignore.short": "Ignore file template operations",
|
||||
"cmd.issue.batch_close.long": "Close filtered issues in bulk.\n\nThis command defaults to dry-run mode and only prints matching issues.\nPass --yes to execute remote close operations. Use restrictive filters and a small limit.\n\nExamples:\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
|
||||
"cmd.issue.batch_close.short": "Close filtered issues in bulk. Defaults to dry-run; pass --yes to execute.",
|
||||
"cmd.issue.batch_label.long": "Add a label to filtered issues in bulk.\n\nThis command defaults to dry-run mode and only prints matching issues.\nPass --yes to execute remote label operations. The current implementation does not fake label writes when the API endpoint is unavailable.\n\nExamples:\n gitlink-cli issue +batch-label --owner Gitlink --repo gitlink-cli --add-label stale --older-than-days 30 --limit 50\n gitlink-cli issue +batch-label --owner Gitlink --repo gitlink-cli --add-label stale --older-than-days 30 --limit 50 --yes",
|
||||
|
|
@ -54,11 +61,46 @@
|
|||
"cmd.issue.short": "Issue operations",
|
||||
"cmd.issue.update.short": "Update an issue",
|
||||
"cmd.issue.view.short": "View issue details",
|
||||
"cmd.label.create.short": "Create an issue label",
|
||||
"cmd.label.delete.short": "Delete an issue label",
|
||||
"cmd.label.list.short": "List issue labels",
|
||||
"cmd.label.short": "Issue label operations",
|
||||
"cmd.label.update.short": "Update an issue label while preserving unspecified fields",
|
||||
"cmd.license.list.short": "List available licenses",
|
||||
"cmd.license.short": "License operations",
|
||||
"cmd.member.accept-invite.short": "Accept a repository invite link",
|
||||
"cmd.member.add.short": "Add a repository member by user ID",
|
||||
"cmd.member.batch-add.short": "Add multiple repository members by user IDs or a CSV file",
|
||||
"cmd.member.invite-info.short": "Show repository invite link information",
|
||||
"cmd.member.invite-link.short": "Get or create a repository invite link",
|
||||
"cmd.member.list.short": "List repository members",
|
||||
"cmd.member.remove.short": "Remove a repository member by user ID",
|
||||
"cmd.member.role.short": "Change a repository member role",
|
||||
"cmd.member.short": "Repository member operations",
|
||||
"cmd.milestone.close.short": "Close a milestone",
|
||||
"cmd.milestone.create.short": "Create a milestone",
|
||||
"cmd.milestone.delete.short": "Delete a milestone",
|
||||
"cmd.milestone.list.short": "List milestones",
|
||||
"cmd.milestone.reopen.short": "Reopen a milestone",
|
||||
"cmd.milestone.short": "Milestone operations",
|
||||
"cmd.milestone.update.short": "Update a milestone",
|
||||
"cmd.milestone.view.short": "View milestone details and linked issues",
|
||||
"cmd.org.create.short": "Create an organization",
|
||||
"cmd.org.info.short": "Show organization details",
|
||||
"cmd.org.list.short": "List organizations",
|
||||
"cmd.org.members.short": "List organization members",
|
||||
"cmd.org.short": "Organization operations",
|
||||
"cmd.pipeline.delete.short": "Delete a pipeline",
|
||||
"cmd.pipeline.disable.short": "Disable a pipeline workflow",
|
||||
"cmd.pipeline.enable.short": "Enable a pipeline workflow",
|
||||
"cmd.pipeline.list.short": "List platform pipelines",
|
||||
"cmd.pipeline.logs.short": "Query pipeline run logs",
|
||||
"cmd.pipeline.results.short": "Show pipeline run report results",
|
||||
"cmd.pipeline.run.short": "Run a pipeline workflow",
|
||||
"cmd.pipeline.runs.short": "List pipeline run records",
|
||||
"cmd.pipeline.save-yaml.short": "Save a visual pipeline YAML graph",
|
||||
"cmd.pipeline.short": "Pipeline operations",
|
||||
"cmd.pipeline.view.short": "Show pipeline details",
|
||||
"cmd.pr.close.short": "Close a pull request",
|
||||
"cmd.pr.comment.short": "Add a comment to a pull request",
|
||||
"cmd.pr.create.short": "Create a pull request",
|
||||
|
|
@ -114,6 +156,17 @@
|
|||
"cmd.webhook.test.short": "Trigger a test delivery for a webhook",
|
||||
"cmd.webhook.update.short": "Update a repository webhook while preserving unspecified fields when available",
|
||||
"cmd.webhook.view.short": "View webhook details",
|
||||
"cmd.wiki.create.short": "Create a new wiki page",
|
||||
"cmd.wiki.delete.short": "Delete a wiki page",
|
||||
"cmd.wiki.list.short": "List wiki pages",
|
||||
"cmd.wiki.short": "Wiki page management",
|
||||
"cmd.wiki.update.short": "Update an existing wiki page",
|
||||
"cmd.wiki.view.short": "View a wiki page by page name",
|
||||
"cmd.workflow.health.short": "Score repository health with local workflow rules",
|
||||
"cmd.workflow.pr-summary.short": "Generate a read-only pull request review summary",
|
||||
"cmd.workflow.repo-report.short": "Generate a read-only repository workflow report",
|
||||
"cmd.workflow.short": "AI agent workflow analysis",
|
||||
"cmd.workflow.triage.short": "Analyze issues with local workflow triage rules",
|
||||
"error.auth.delete_token_failed": "failed to delete token: {message}",
|
||||
"error.auth.login_failed": "login failed: {message}",
|
||||
"error.auth.store_token_failed": "failed to store token: {message}",
|
||||
|
|
@ -145,6 +198,9 @@
|
|||
"flag.ci.stage": "Stage number",
|
||||
"flag.ci.step": "Step number",
|
||||
"flag.comment.body": "Comment body",
|
||||
"flag.compare.base": "Target branch, tag, or commit",
|
||||
"flag.compare.file": "Filter by file path",
|
||||
"flag.compare.head": "Source branch, tag, or commit",
|
||||
"flag.dataset.description": "Dataset description",
|
||||
"flag.dataset.dry_run": "Preview the request without writing the dataset",
|
||||
"flag.dataset.dry_run_delete": "Preview the request without deleting the attachment",
|
||||
|
|
@ -161,6 +217,9 @@
|
|||
"flag.doctor.skip_network": "Skip authenticated API connectivity checks",
|
||||
"flag.dry_run": "Preview the request without creating it",
|
||||
"flag.format": "Output format: json, table, yaml (default: table)",
|
||||
"flag.health.db": "SQLite database path (default: ~/.agents/skills/gitlink-health/data/gitlink_health.db)",
|
||||
"flag.health.max_pages": "Maximum pages per query (default: unlimited)",
|
||||
"flag.ignore.name": "Filter ignore templates by name",
|
||||
"flag.issue.add_label": "Label to add to each matching issue",
|
||||
"flag.issue.assignee": "Assignee login",
|
||||
"flag.issue.assignee_id": "Assignee user ID",
|
||||
|
|
@ -186,13 +245,57 @@
|
|||
"flag.issue.status_id": "Issue status ID",
|
||||
"flag.issue.tag_ids": "Comma-separated issue tag IDs",
|
||||
"flag.issue.title": "Issue title",
|
||||
"flag.label.color": "Label color in hex, for example: #1E90FF",
|
||||
"flag.label.description": "Label description",
|
||||
"flag.label.id": "Label ID",
|
||||
"flag.label.keyword": "Filter labels by keyword",
|
||||
"flag.label.name": "Label name",
|
||||
"flag.label.only_name": "Return only label id and name: true or false",
|
||||
"flag.label.sort_by": "Sort field: updated_on, created_on, issues_count",
|
||||
"flag.label.sort_direction": "Sort direction: asc or desc",
|
||||
"flag.lang": "Display language",
|
||||
"flag.license.name": "Filter licenses by name",
|
||||
"flag.limit": "Items per page",
|
||||
"flag.member.apply": "Whether joining by invite requires approval: true or false",
|
||||
"flag.member.dry_run": "Preview members that would be added without changing them",
|
||||
"flag.member.from": "Read user IDs from a CSV file. Supports a user_id/id column or first column without header",
|
||||
"flag.member.role": "Member role: Manager, Developer, or Reporter",
|
||||
"flag.member.role_2": "Invite role: manager, developer, or reporter",
|
||||
"flag.member.sign": "Invite link sign",
|
||||
"flag.member.user_id": "GitLink user ID to add",
|
||||
"flag.member.user_id_2": "GitLink user ID to update",
|
||||
"flag.member.user_ids": "Comma-separated GitLink user IDs, for example: 101,102",
|
||||
"flag.milestone.assigner_id": "Filter issues by assignee ID",
|
||||
"flag.milestone.author_id": "Filter issues by author ID",
|
||||
"flag.milestone.category": "Filter by category: opening, closed",
|
||||
"flag.milestone.category_2": "Filter issues by category: all, opened, closed",
|
||||
"flag.milestone.description": "Milestone description",
|
||||
"flag.milestone.due_date": "Due date in YYYY-MM-DD format",
|
||||
"flag.milestone.id": "Milestone ID",
|
||||
"flag.milestone.issue_tag_ids": "Comma-separated issue tag IDs",
|
||||
"flag.milestone.name": "Milestone name",
|
||||
"flag.milestone.only_name": "Return only milestone id and name: true or false",
|
||||
"flag.milestone.sort_by": "Sort field: created_on, updated_on, effective_date, issues_count, percent",
|
||||
"flag.milestone.sort_by_2": "Sort field: issues.created_on, issues.updated_on, issue_priorities.position",
|
||||
"flag.milestone.sort_direction": "Sort direction: asc or desc",
|
||||
"flag.org.id": "Organization ID",
|
||||
"flag.org.id_or_login": "Organization ID or login",
|
||||
"flag.org.name": "Organization name",
|
||||
"flag.owner": "Repository owner (auto-detected from git remote)",
|
||||
"flag.page": "Page number",
|
||||
"flag.pipeline.cursor": "Log cursor",
|
||||
"flag.pipeline.dry_run": "Preview the run request without starting a pipeline",
|
||||
"flag.pipeline.dry_run_2": "Preview the request without changing pipeline state",
|
||||
"flag.pipeline.expanded": "Whether the log cursor is expanded",
|
||||
"flag.pipeline.id": "Pipeline ID",
|
||||
"flag.pipeline.index": "Run index",
|
||||
"flag.pipeline.job": "Job index",
|
||||
"flag.pipeline.owner_id": "Owner user or organization ID",
|
||||
"flag.pipeline.pipeline_json": "Pipeline graph JSON object or string",
|
||||
"flag.pipeline.ref": "Branch, tag, or commit SHA",
|
||||
"flag.pipeline.run_id": "Pipeline run ID",
|
||||
"flag.pipeline.step": "Log step",
|
||||
"flag.pipeline.workflow": "Workflow file name",
|
||||
"flag.pr.assignee_id": "Assignee user ID",
|
||||
"flag.pr.base": "Target branch",
|
||||
"flag.pr.body": "PR description",
|
||||
|
|
@ -246,6 +349,58 @@
|
|||
"flag.webhook.secret_update": "Webhook secret. Pass it again if the server does not return existing secrets.",
|
||||
"flag.webhook.type": "Webhook type: gitea/slack/discord/dingtalk/telegram/msteams/feishu/matrix/jianmu/softbot",
|
||||
"flag.webhook.url": "Webhook target URL",
|
||||
"flag.wiki.content": "Wiki page content (markdown)",
|
||||
"flag.wiki.gateway": "Use gateway API endpoint",
|
||||
"flag.wiki.message": "Commit message",
|
||||
"flag.wiki.page_name": "Wiki page name (slug)",
|
||||
"flag.wiki.project_id": "GitLink project ID",
|
||||
"flag.wiki.title": "Wiki page title",
|
||||
"flag.workflow.agent_readiness_known": "Whether agent readiness score is known",
|
||||
"flag.workflow.agent_readiness_score": "Agent readiness score from 0 to 10",
|
||||
"flag.workflow.author": "Issue author for single-issue local analysis",
|
||||
"flag.workflow.body": "Issue body for single-issue local analysis",
|
||||
"flag.workflow.ci_known": "Whether CI status is known",
|
||||
"flag.workflow.ci_passing": "Whether CI is passing",
|
||||
"flag.workflow.dry_run": "Preview workflow recommendations without remote writes",
|
||||
"flag.workflow.from": "Read issue inputs from a JSON file. Supports a single issue, an array, or an object with an issues field",
|
||||
"flag.workflow.from_2": "Read health input from a JSON file",
|
||||
"flag.workflow.has_contributing": "Whether CONTRIBUTING exists",
|
||||
"flag.workflow.has_license": "Whether LICENSE exists",
|
||||
"flag.workflow.has_readme": "Whether README exists",
|
||||
"flag.workflow.has_recent_release": "Whether a recent release exists",
|
||||
"flag.workflow.labels": "Comma-separated labels for single-issue local analysis",
|
||||
"flag.workflow.lang": "Output language: en or zh-CN",
|
||||
"flag.workflow.limit": "Maximum issues to analyze",
|
||||
"flag.workflow.number": "Issue number for single-issue local analysis",
|
||||
"flag.workflow.open_issues": "Open issue count",
|
||||
"flag.workflow.open_prs": "Open pull request count",
|
||||
"flag.workflow.page": "API page number for remote triage",
|
||||
"flag.workflow.pr_summary.from": "Read PR summary input from a JSON file",
|
||||
"flag.workflow.pr_summary.include_commits": "Fetch commits in remote mode",
|
||||
"flag.workflow.pr_summary.include_files": "Fetch changed files in remote mode",
|
||||
"flag.workflow.pr_summary.lang": "Output language: en or zh-CN",
|
||||
"flag.workflow.pr_summary.max_commits": "Maximum commits to analyze",
|
||||
"flag.workflow.pr_summary.max_files": "Maximum changed files to analyze",
|
||||
"flag.workflow.pr_summary.number": "Pull request number for remote read-only analysis",
|
||||
"flag.workflow.recent_activity_days": "Days since recent activity",
|
||||
"flag.workflow.recent_activity_known": "Whether recent activity is known",
|
||||
"flag.workflow.release_known": "Whether release status is known",
|
||||
"flag.workflow.repo_report.from": "Read repository report input from a JSON file",
|
||||
"flag.workflow.repo_report.include_health": "Include repository health summary",
|
||||
"flag.workflow.repo_report.include_issues": "Include issue triage summary",
|
||||
"flag.workflow.repo_report.include_prs": "Include pull request summary",
|
||||
"flag.workflow.repo_report.issue_limit": "Maximum issues to fetch and analyze",
|
||||
"flag.workflow.repo_report.lang": "Output language: en or zh-CN",
|
||||
"flag.workflow.repo_report.pr_limit": "Maximum pull requests to fetch and summarize",
|
||||
"flag.workflow.repo_report.stale_days": "Days before an issue or PR is considered stale",
|
||||
"flag.workflow.repository": "Repository name, for example owner/repo",
|
||||
"flag.workflow.since": "Optional remote issue filter for updated time",
|
||||
"flag.workflow.stale_days": "Days before an issue or PR is considered stale",
|
||||
"flag.workflow.stale_issues": "Stale issue count",
|
||||
"flag.workflow.stale_prs": "Stale pull request count",
|
||||
"flag.workflow.state": "Filter or assign issue state",
|
||||
"flag.workflow.title": "Issue title for single-issue local analysis",
|
||||
"flag.workflow.url": "Issue URL for single-issue local analysis",
|
||||
"output.auth.env_hint": " Or set {env} environment variable",
|
||||
"output.auth.login_hint": " Run: gitlink-cli auth login",
|
||||
"output.config.file": "Config file: {path}",
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
"cmd.ci.restart.short": "重启构建",
|
||||
"cmd.ci.short": "CI/CD 操作",
|
||||
"cmd.ci.stop.short": "停止构建",
|
||||
"cmd.compare.files.short": "列出两个引用之间变更的文件",
|
||||
"cmd.compare.short": "比较分支、标签或提交",
|
||||
"cmd.compare.view.short": "比较两个分支、标签或提交",
|
||||
"cmd.config.get.short": "获取配置项",
|
||||
"cmd.config.init.short": "初始化配置文件",
|
||||
"cmd.config.list.short": "列出所有配置项",
|
||||
|
|
@ -41,6 +44,10 @@
|
|||
"cmd.dataset.view.short": "查看仓库数据集",
|
||||
"cmd.doctor.long": "诊断 gitlink-cli 的配置、认证、仓库上下文和 API 连通性问题。",
|
||||
"cmd.doctor.short": "诊断 gitlink-cli 环境问题",
|
||||
"cmd.health.fetch.short": "抓取 PR 与 Issue 数据到 SQLite 用于健康度分析",
|
||||
"cmd.health.short": "项目健康数据采集",
|
||||
"cmd.ignore.list.short": "列出可用的 ignore 文件模板",
|
||||
"cmd.ignore.short": "忽略文件模板操作",
|
||||
"cmd.issue.batch_close.long": "批量关闭筛选后的议题。\n\n该命令默认处于 dry-run 模式,只打印匹配的议题。\n传入 --yes 后执行远端关闭操作。请使用严格筛选条件和较小 limit。\n\n示例:\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
|
||||
"cmd.issue.batch_close.short": "批量关闭筛选后的议题。默认 dry-run;传入 --yes 后执行。",
|
||||
"cmd.issue.batch_label.long": "给筛选后的议题批量添加标签。\n\n该命令默认处于 dry-run 模式,只打印匹配的议题。\n传入 --yes 后执行远端标签操作。当前实现不会在 API 端点不可用时伪造写入结果。\n\n示例:\n gitlink-cli issue +batch-label --owner Gitlink --repo gitlink-cli --add-label stale --older-than-days 30 --limit 50\n gitlink-cli issue +batch-label --owner Gitlink --repo gitlink-cli --add-label stale --older-than-days 30 --limit 50 --yes",
|
||||
|
|
@ -54,11 +61,46 @@
|
|||
"cmd.issue.short": "议题操作",
|
||||
"cmd.issue.update.short": "更新议题",
|
||||
"cmd.issue.view.short": "查看议题详情",
|
||||
"cmd.label.create.short": "创建 issue 标签",
|
||||
"cmd.label.delete.short": "删除 issue 标签",
|
||||
"cmd.label.list.short": "列出 issue 标签",
|
||||
"cmd.label.short": "Issue 标签操作",
|
||||
"cmd.label.update.short": "更新 issue 标签(未指定字段保持不变)",
|
||||
"cmd.license.list.short": "列出可用的开源许可证",
|
||||
"cmd.license.short": "许可证操作",
|
||||
"cmd.member.accept-invite.short": "接受仓库邀请链接",
|
||||
"cmd.member.add.short": "按用户 ID 添加仓库成员",
|
||||
"cmd.member.batch-add.short": "按用户 ID 列表或 CSV 文件批量添加仓库成员",
|
||||
"cmd.member.invite-info.short": "查看仓库邀请链接信息",
|
||||
"cmd.member.invite-link.short": "获取或创建仓库邀请链接",
|
||||
"cmd.member.list.short": "列出仓库成员",
|
||||
"cmd.member.remove.short": "按用户 ID 移除仓库成员",
|
||||
"cmd.member.role.short": "变更仓库成员角色",
|
||||
"cmd.member.short": "仓库成员操作",
|
||||
"cmd.milestone.close.short": "关闭里程碑",
|
||||
"cmd.milestone.create.short": "创建里程碑",
|
||||
"cmd.milestone.delete.short": "删除里程碑",
|
||||
"cmd.milestone.list.short": "列出里程碑",
|
||||
"cmd.milestone.reopen.short": "重新打开里程碑",
|
||||
"cmd.milestone.short": "里程碑操作",
|
||||
"cmd.milestone.update.short": "更新里程碑",
|
||||
"cmd.milestone.view.short": "查看里程碑详情及关联 issue",
|
||||
"cmd.org.create.short": "创建组织",
|
||||
"cmd.org.info.short": "显示组织详情",
|
||||
"cmd.org.list.short": "列出组织",
|
||||
"cmd.org.members.short": "列出组织成员",
|
||||
"cmd.org.short": "组织操作",
|
||||
"cmd.pipeline.delete.short": "删除流水线",
|
||||
"cmd.pipeline.disable.short": "停用流水线工作流",
|
||||
"cmd.pipeline.enable.short": "启用流水线工作流",
|
||||
"cmd.pipeline.list.short": "列出平台流水线",
|
||||
"cmd.pipeline.logs.short": "查询流水线运行日志",
|
||||
"cmd.pipeline.results.short": "查看流水线运行报告结果",
|
||||
"cmd.pipeline.run.short": "运行流水线工作流",
|
||||
"cmd.pipeline.runs.short": "列出流水线运行记录",
|
||||
"cmd.pipeline.save-yaml.short": "保存可视化流水线 YAML 图",
|
||||
"cmd.pipeline.short": "流水线操作",
|
||||
"cmd.pipeline.view.short": "查看流水线详情",
|
||||
"cmd.pr.close.short": "关闭拉取请求",
|
||||
"cmd.pr.comment.short": "给拉取请求添加评论",
|
||||
"cmd.pr.create.short": "创建拉取请求",
|
||||
|
|
@ -114,6 +156,17 @@
|
|||
"cmd.webhook.test.short": "触发 Webhook 测试投递",
|
||||
"cmd.webhook.update.short": "更新仓库 Webhook,并在可用时保留未指定字段",
|
||||
"cmd.webhook.view.short": "查看 Webhook 详情",
|
||||
"cmd.wiki.create.short": "创建 wiki 页面",
|
||||
"cmd.wiki.delete.short": "删除 wiki 页面",
|
||||
"cmd.wiki.list.short": "列出 wiki 页面",
|
||||
"cmd.wiki.short": "Wiki 页面管理",
|
||||
"cmd.wiki.update.short": "更新已有 wiki 页面",
|
||||
"cmd.wiki.view.short": "按页面名查看 wiki 页面",
|
||||
"cmd.workflow.health.short": "用本地工作流规则为仓库健康度打分",
|
||||
"cmd.workflow.pr-summary.short": "生成只读的 PR 评审摘要",
|
||||
"cmd.workflow.repo-report.short": "生成只读的仓库工作流报告",
|
||||
"cmd.workflow.short": "AI 智能体工作流分析",
|
||||
"cmd.workflow.triage.short": "用本地工作流分诊规则分析 issue",
|
||||
"error.auth.delete_token_failed": "删除 Token 失败:{message}",
|
||||
"error.auth.login_failed": "登录失败:{message}",
|
||||
"error.auth.store_token_failed": "保存 Token 失败:{message}",
|
||||
|
|
@ -145,6 +198,9 @@
|
|||
"flag.ci.stage": "阶段编号",
|
||||
"flag.ci.step": "步骤编号",
|
||||
"flag.comment.body": "评论内容",
|
||||
"flag.compare.base": "目标分支、标签或提交",
|
||||
"flag.compare.file": "按文件路径过滤",
|
||||
"flag.compare.head": "源分支、标签或提交",
|
||||
"flag.dataset.description": "数据集描述",
|
||||
"flag.dataset.dry_run": "预览请求,不写入数据集",
|
||||
"flag.dataset.dry_run_delete": "预览请求,不删除附件",
|
||||
|
|
@ -161,6 +217,9 @@
|
|||
"flag.doctor.skip_network": "跳过需要访问 GitLink 的认证连通性检查",
|
||||
"flag.dry_run": "预览请求,不实际创建",
|
||||
"flag.format": "输出格式:json、table、yaml(默认:table)",
|
||||
"flag.health.db": "SQLite 数据库路径(默认:~/.agents/skills/gitlink-health/data/gitlink_health.db)",
|
||||
"flag.health.max_pages": "每次查询的最大页数(默认:不限)",
|
||||
"flag.ignore.name": "按名称过滤 ignore 模板",
|
||||
"flag.issue.add_label": "要添加到每个匹配议题的标签",
|
||||
"flag.issue.assignee": "负责人登录名",
|
||||
"flag.issue.assignee_id": "负责人用户 ID",
|
||||
|
|
@ -186,13 +245,57 @@
|
|||
"flag.issue.status_id": "议题状态 ID",
|
||||
"flag.issue.tag_ids": "逗号分隔的议题标签 ID",
|
||||
"flag.issue.title": "议题标题",
|
||||
"flag.label.color": "标签颜色(十六进制),例如:#1E90FF",
|
||||
"flag.label.description": "标签描述",
|
||||
"flag.label.id": "标签 ID",
|
||||
"flag.label.keyword": "按关键词过滤标签",
|
||||
"flag.label.name": "标签名称",
|
||||
"flag.label.only_name": "仅返回标签 id 和名称:true 或 false",
|
||||
"flag.label.sort_by": "排序字段:updated_on、created_on、issues_count",
|
||||
"flag.label.sort_direction": "排序方向:asc 或 desc",
|
||||
"flag.lang": "显示语言",
|
||||
"flag.license.name": "按名称过滤许可证",
|
||||
"flag.limit": "每页条目数",
|
||||
"flag.member.apply": "通过邀请加入是否需要审批:true 或 false",
|
||||
"flag.member.dry_run": "仅预览将添加的成员,不实际变更",
|
||||
"flag.member.from": "从 CSV 文件读取用户 ID。支持 user_id/id 列或无表头的第一列",
|
||||
"flag.member.role": "成员角色:Manager、Developer 或 Reporter",
|
||||
"flag.member.role_2": "邀请角色:manager、developer 或 reporter",
|
||||
"flag.member.sign": "邀请链接签名",
|
||||
"flag.member.user_id": "要添加的 GitLink 用户 ID",
|
||||
"flag.member.user_id_2": "要更新的 GitLink 用户 ID",
|
||||
"flag.member.user_ids": "逗号分隔的 GitLink 用户 ID,例如:101,102",
|
||||
"flag.milestone.assigner_id": "按负责人 ID 过滤 issue",
|
||||
"flag.milestone.author_id": "按作者 ID 过滤 issue",
|
||||
"flag.milestone.category": "按类别过滤:opening、closed",
|
||||
"flag.milestone.category_2": "按类别过滤 issue:all、opened、closed",
|
||||
"flag.milestone.description": "里程碑描述",
|
||||
"flag.milestone.due_date": "截止日期(YYYY-MM-DD 格式)",
|
||||
"flag.milestone.id": "里程碑 ID",
|
||||
"flag.milestone.issue_tag_ids": "逗号分隔的 issue 标签 ID",
|
||||
"flag.milestone.name": "里程碑名称",
|
||||
"flag.milestone.only_name": "仅返回里程碑 id 和名称:true 或 false",
|
||||
"flag.milestone.sort_by": "排序字段:created_on、updated_on、effective_date、issues_count、percent",
|
||||
"flag.milestone.sort_by_2": "排序字段:issues.created_on、issues.updated_on、issue_priorities.position",
|
||||
"flag.milestone.sort_direction": "排序方向:asc 或 desc",
|
||||
"flag.org.id": "组织 ID",
|
||||
"flag.org.id_or_login": "组织 ID 或登录名",
|
||||
"flag.org.name": "组织名称",
|
||||
"flag.owner": "仓库所有者(自动从 git remote 检测)",
|
||||
"flag.page": "页码",
|
||||
"flag.pipeline.cursor": "日志游标",
|
||||
"flag.pipeline.dry_run": "仅预览运行请求,不实际启动流水线",
|
||||
"flag.pipeline.dry_run_2": "仅预览请求,不变更流水线状态",
|
||||
"flag.pipeline.expanded": "日志游标是否展开",
|
||||
"flag.pipeline.id": "流水线 ID",
|
||||
"flag.pipeline.index": "运行序号",
|
||||
"flag.pipeline.job": "任务序号",
|
||||
"flag.pipeline.owner_id": "所有者用户或组织 ID",
|
||||
"flag.pipeline.pipeline_json": "流水线图 JSON 对象或字符串",
|
||||
"flag.pipeline.ref": "分支、标签或提交 SHA",
|
||||
"flag.pipeline.run_id": "流水线运行 ID",
|
||||
"flag.pipeline.step": "日志步骤",
|
||||
"flag.pipeline.workflow": "工作流文件名",
|
||||
"flag.pr.assignee_id": "指派人用户 ID",
|
||||
"flag.pr.base": "目标分支",
|
||||
"flag.pr.body": "PR 描述",
|
||||
|
|
@ -246,6 +349,58 @@
|
|||
"flag.webhook.secret_update": "Webhook 密钥。如果服务端不返回已有密钥,请再次传入。",
|
||||
"flag.webhook.type": "Webhook 类型:gitea/slack/discord/dingtalk/telegram/msteams/feishu/matrix/jianmu/softbot",
|
||||
"flag.webhook.url": "Webhook 目标 URL",
|
||||
"flag.wiki.content": "wiki 页面内容(markdown)",
|
||||
"flag.wiki.gateway": "使用网关 API 端点",
|
||||
"flag.wiki.message": "提交说明",
|
||||
"flag.wiki.page_name": "wiki 页面名(slug)",
|
||||
"flag.wiki.project_id": "GitLink 项目 ID",
|
||||
"flag.wiki.title": "wiki 页面标题",
|
||||
"flag.workflow.agent_readiness_known": "Agent 就绪度得分是否已知",
|
||||
"flag.workflow.agent_readiness_score": "Agent 就绪度得分(0 到 10)",
|
||||
"flag.workflow.author": "单 issue 本地分析的 issue 作者",
|
||||
"flag.workflow.body": "单 issue 本地分析的 issue 正文",
|
||||
"flag.workflow.ci_known": "CI 状态是否已知",
|
||||
"flag.workflow.ci_passing": "CI 是否通过",
|
||||
"flag.workflow.dry_run": "仅预览工作流建议,不做远程写入",
|
||||
"flag.workflow.from": "从 JSON 文件读取 issue 输入。支持单个 issue、数组或含 issues 字段的对象",
|
||||
"flag.workflow.from_2": "从 JSON 文件读取健康度输入",
|
||||
"flag.workflow.has_contributing": "是否存在 CONTRIBUTING",
|
||||
"flag.workflow.has_license": "是否存在 LICENSE",
|
||||
"flag.workflow.has_readme": "是否存在 README",
|
||||
"flag.workflow.has_recent_release": "是否有近期 release",
|
||||
"flag.workflow.labels": "单 issue 本地分析的逗号分隔标签",
|
||||
"flag.workflow.lang": "输出语言:en 或 zh-CN",
|
||||
"flag.workflow.limit": "分析的最大 issue 数",
|
||||
"flag.workflow.number": "单 issue 本地分析的 issue 编号",
|
||||
"flag.workflow.open_issues": "打开的 issue 数",
|
||||
"flag.workflow.open_prs": "打开的 PR 数",
|
||||
"flag.workflow.page": "远程分诊的 API 页码",
|
||||
"flag.workflow.pr_summary.from": "从 JSON 文件读取 PR 摘要输入",
|
||||
"flag.workflow.pr_summary.include_commits": "远程模式下抓取提交",
|
||||
"flag.workflow.pr_summary.include_files": "远程模式下抓取变更文件",
|
||||
"flag.workflow.pr_summary.lang": "输出语言:en 或 zh-CN",
|
||||
"flag.workflow.pr_summary.max_commits": "分析的最大提交数",
|
||||
"flag.workflow.pr_summary.max_files": "分析的最大变更文件数",
|
||||
"flag.workflow.pr_summary.number": "远程只读分析的 PR 编号",
|
||||
"flag.workflow.recent_activity_days": "距最近活动的天数",
|
||||
"flag.workflow.recent_activity_known": "最近活动是否已知",
|
||||
"flag.workflow.release_known": "release 状态是否已知",
|
||||
"flag.workflow.repo_report.from": "从 JSON 文件读取仓库报告输入",
|
||||
"flag.workflow.repo_report.include_health": "包含仓库健康度摘要",
|
||||
"flag.workflow.repo_report.include_issues": "包含 issue 分诊摘要",
|
||||
"flag.workflow.repo_report.include_prs": "包含 PR 摘要",
|
||||
"flag.workflow.repo_report.issue_limit": "抓取并分析的最大 issue 数",
|
||||
"flag.workflow.repo_report.lang": "输出语言:en 或 zh-CN",
|
||||
"flag.workflow.repo_report.pr_limit": "抓取并汇总的最大 PR 数",
|
||||
"flag.workflow.repo_report.stale_days": "issue 或 PR 视为停滞的天数",
|
||||
"flag.workflow.repository": "仓库名,例如 owner/repo",
|
||||
"flag.workflow.since": "可选的远程 issue 更新时间过滤",
|
||||
"flag.workflow.stale_days": "issue 或 PR 视为停滞的天数",
|
||||
"flag.workflow.stale_issues": "停滞 issue 数",
|
||||
"flag.workflow.stale_prs": "停滞 PR 数",
|
||||
"flag.workflow.state": "过滤或指定 issue 状态",
|
||||
"flag.workflow.title": "单 issue 本地分析的 issue 标题",
|
||||
"flag.workflow.url": "单 issue 本地分析的 issue URL",
|
||||
"output.auth.env_hint": " 或设置 {env} 环境变量",
|
||||
"output.auth.login_hint": " 运行:gitlink-cli auth login",
|
||||
"output.config.file": "配置文件:{path}",
|
||||
|
|
|
|||
|
|
@ -4,25 +4,30 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "view",
|
||||
Description: "Compare two branches, tags, or commits",
|
||||
Description: tr.T("cmd.compare.view.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "head", Usage: "Source branch, tag, or commit", Required: true},
|
||||
{Name: "base", Usage: "Target branch, tag, or commit", Required: true},
|
||||
{Name: "head", Usage: tr.T("flag.compare.head"), Required: true},
|
||||
{Name: "base", Usage: tr.T("flag.compare.base"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
head, base, err := resolveCompareRefs(ctx)
|
||||
head, err := ctx.RequireArg("head")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
base, err := ctx.RequireArg("base")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -35,33 +40,29 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "files",
|
||||
Description: "List changed files between two refs",
|
||||
Description: tr.T("cmd.compare.files.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "head", Usage: "Source branch, tag, or commit", Required: true},
|
||||
{Name: "base", Usage: "Target branch, tag, or commit", Required: true},
|
||||
{Name: "file", Short: "f", Usage: "Filter by file path"},
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
{Name: "head", Usage: tr.T("flag.compare.head"), Required: true},
|
||||
{Name: "base", Usage: tr.T("flag.compare.base"), Required: true},
|
||||
{Name: "file", Short: "f", Usage: tr.T("flag.compare.file")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
head, base, err := resolveCompareRefs(ctx)
|
||||
head, err := ctx.RequireArg("head")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
page, err := parsePositiveIntArg(ctx.Arg("page"), 1, "page")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
limit, err := parsePositiveIntArg(ctx.Arg("limit"), 20, "limit")
|
||||
base, err := ctx.RequireArg("base")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("page", strconv.Itoa(page))
|
||||
q.Set("limit", strconv.Itoa(limit))
|
||||
q.Set("page", ctx.Arg("page"))
|
||||
q.Set("limit", ctx.Arg("limit"))
|
||||
if file := ctx.Arg("file"); file != "" {
|
||||
q.Set("filepath", file)
|
||||
}
|
||||
|
|
@ -72,31 +73,6 @@ func Shortcuts() []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commits",
|
||||
Description: "List commits between two refs with optional filters",
|
||||
Flags: []common.Flag{
|
||||
{Name: "head", Usage: "Source branch, tag, or commit", Required: true},
|
||||
{Name: "base", Usage: "Target branch, tag, or commit", Required: true},
|
||||
{Name: "author", Usage: "Filter by commit author or committer"},
|
||||
{Name: "keyword", Short: "k", Usage: "Filter by commit message keyword"},
|
||||
{Name: "limit", Short: "l", Usage: "Maximum commits to return", Default: "20"},
|
||||
{Name: "reverse", Usage: "Return commits in reverse order", Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runCompareCommits,
|
||||
},
|
||||
{
|
||||
Name: "summary",
|
||||
Description: "Summarize commits and changed files between two refs",
|
||||
Flags: []common.Flag{
|
||||
{Name: "head", Usage: "Source branch, tag, or commit", Required: true},
|
||||
{Name: "base", Usage: "Target branch, tag, or commit", Required: true},
|
||||
{Name: "max-files", Usage: "Maximum changed files to analyze", Default: "200"},
|
||||
{Name: "top-files", Usage: "Maximum top changed files to include", Default: "10"},
|
||||
{Name: "commit-limit", Usage: "Maximum commits to include in the summary sample", Default: "10"},
|
||||
},
|
||||
Run: runCompareSummary,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -108,14 +84,9 @@ func encodeRef(ref string) string {
|
|||
return base64.RawURLEncoding.EncodeToString([]byte(ref))
|
||||
}
|
||||
|
||||
func resolveCompareRefs(ctx *common.RuntimeContext) (string, string, error) {
|
||||
head, err := ctx.RequireArg("head")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
base, err := ctx.RequireArg("base")
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
return head, base, nil
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,18 +16,14 @@ import (
|
|||
)
|
||||
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := i18n.Default()
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
tr = translators[0]
|
||||
}
|
||||
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "fetch",
|
||||
Description: "Fetch PR and Issue data into SQLite for health analysis",
|
||||
Description: tr.T("cmd.health.fetch.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "db", Short: "d", Usage: "SQLite database path (default: ~/.agents/skills/gitlink-health/data/gitlink_health.db)"},
|
||||
{Name: "max-pages", Short: "M", Usage: "Maximum pages per query (default: unlimited)"},
|
||||
{Name: "db", Short: "d", Usage: tr.T("flag.health.db")},
|
||||
{Name: "max-pages", Short: "M", Usage: tr.T("flag.health.max_pages")},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -53,7 +49,7 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = db.Close() }()
|
||||
defer db.Close()
|
||||
|
||||
repoID, err := getOrCreateRepo(db, ctx.Repo, ctx.Owner)
|
||||
if err != nil {
|
||||
|
|
@ -97,7 +93,6 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
return nil
|
||||
},
|
||||
},
|
||||
diagnoseShortcut(tr),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -230,3 +225,10 @@ func fetchIssues(ctx *common.RuntimeContext, db *sql.DB, repoID int, state strin
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package ignore
|
|||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
|
|
@ -10,13 +11,14 @@ import (
|
|||
//
|
||||
// These shortcuts provide access to the GitLink ignore-file registry,
|
||||
// which lists all available .gitignore templates supported by the platform.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List available ignore-file templates",
|
||||
Description: tr.T("cmd.ignore.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Filter ignore templates by name"},
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.ignore.name")},
|
||||
},
|
||||
Run: runList,
|
||||
},
|
||||
|
|
@ -34,3 +36,10 @@ func runList(ctx *common.RuntimeContext) error {
|
|||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
|
|
@ -22,16 +23,17 @@ var hexColorPattern = regexp.MustCompile(`^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$`)
|
|||
// Issue labels back the issue triage and PR gatekeeping workflows: until now
|
||||
// they could only be managed through the raw API (issue_tags), so these
|
||||
// shortcuts close that gap with first-class create/list/update/delete commands.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List issue labels",
|
||||
Description: tr.T("cmd.label.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "keyword", Short: "k", Usage: "Filter labels by keyword"},
|
||||
{Name: "only-name", Usage: "Return only label id and name: true or false"},
|
||||
{Name: "sort-by", Usage: "Sort field: updated_on, created_on, issues_count"},
|
||||
{Name: "sort-direction", Usage: "Sort direction: asc or desc"},
|
||||
{Name: "keyword", Short: "k", Usage: tr.T("flag.label.keyword")},
|
||||
{Name: "only-name", Usage: tr.T("flag.label.only_name")},
|
||||
{Name: "sort-by", Usage: tr.T("flag.label.sort_by")},
|
||||
{Name: "sort-direction", Usage: tr.T("flag.label.sort_direction")},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -51,84 +53,49 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "create",
|
||||
Description: "Create an issue label",
|
||||
Description: tr.T("cmd.label.create.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Label name", Required: true},
|
||||
{Name: "description", Short: "d", Usage: "Label description"},
|
||||
{Name: "color", Short: "c", Usage: "Label color in hex, for example: #1E90FF", Default: defaultLabelColor},
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.label.name"), Required: true},
|
||||
{Name: "description", Short: "d", Usage: tr.T("flag.label.description")},
|
||||
{Name: "color", Short: "c", Usage: tr.T("flag.label.color"), Default: defaultLabelColor},
|
||||
},
|
||||
Run: runCreate,
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Description: "Update an issue label while preserving unspecified fields",
|
||||
Description: tr.T("cmd.label.update.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Label ID", Required: true},
|
||||
{Name: "name", Short: "n", Usage: "Label name"},
|
||||
{Name: "description", Short: "d", Usage: "Label description"},
|
||||
{Name: "color", Short: "c", Usage: "Label color in hex, for example: #1E90FF"},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.label.id"), Required: true},
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.label.name")},
|
||||
{Name: "description", Short: "d", Usage: tr.T("flag.label.description")},
|
||||
{Name: "color", Short: "c", Usage: tr.T("flag.label.color")},
|
||||
},
|
||||
Run: runUpdate,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Description: "Delete an issue label",
|
||||
Description: tr.T("cmd.label.delete.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Label ID", Required: true},
|
||||
{Name: "dry-run", Usage: "Preview the delete request without removing the label", Bool: true, Default: "false"},
|
||||
{Name: "yes", Usage: "Confirm label deletion", Bool: true, Default: "false"},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.label.id"), Required: true},
|
||||
},
|
||||
Run: runDelete,
|
||||
},
|
||||
{
|
||||
Name: "batch-create",
|
||||
Description: "Batch create issue labels from semicolon-separated specs",
|
||||
Flags: []common.Flag{
|
||||
{Name: "labels", Usage: "Semicolon-separated label specs: name:color:description", Required: true},
|
||||
{Name: "dry-run", Usage: "Preview labels without creating them", Bool: true, Default: "false"},
|
||||
{Name: "yes", Usage: "Confirm real batch label creation", Bool: true, Default: "false"},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("DELETE", labelItemPath(ctx, id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
Run: runBatchCreate,
|
||||
},
|
||||
{
|
||||
Name: "batch-delete",
|
||||
Description: "Batch delete issue labels by IDs",
|
||||
Flags: []common.Flag{
|
||||
{Name: "ids", Usage: "Comma-separated label IDs", Required: true},
|
||||
{Name: "dry-run", Usage: "Preview labels without deleting them", Bool: true, Default: "false"},
|
||||
{Name: "yes", Usage: "Confirm real batch label deletion", Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runBatchDelete,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type labelSpec struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Description string `json:"description" yaml:"description"`
|
||||
Color string `json:"color" yaml:"color"`
|
||||
}
|
||||
|
||||
type labelBatchRequest struct {
|
||||
Method string `json:"method" yaml:"method"`
|
||||
Path string `json:"path" yaml:"path"`
|
||||
Body map[string]interface{} `json:"body,omitempty" yaml:"body,omitempty"`
|
||||
}
|
||||
|
||||
type labelBatchPreview struct {
|
||||
Repository string `json:"repository" yaml:"repository"`
|
||||
DryRun bool `json:"dry_run" yaml:"dry_run"`
|
||||
Action string `json:"action" yaml:"action"`
|
||||
Requests []labelBatchRequest `json:"requests" yaml:"requests"`
|
||||
}
|
||||
|
||||
type labelBatchResult struct {
|
||||
Request labelBatchRequest `json:"request" yaml:"request"`
|
||||
OK bool `json:"ok" yaml:"ok"`
|
||||
Data interface{} `json:"data,omitempty" yaml:"data,omitempty"`
|
||||
Error string `json:"error,omitempty" yaml:"error,omitempty"`
|
||||
}
|
||||
|
||||
func runCreate(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
@ -198,131 +165,6 @@ func runUpdate(ctx *common.RuntimeContext) error {
|
|||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runDelete(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := labelItemPath(ctx, id)
|
||||
if ctx.Arg("dry-run") == "true" {
|
||||
return ctx.OutputData(labelBatchPreview{
|
||||
Repository: repositoryName(ctx),
|
||||
DryRun: true,
|
||||
Action: "delete_label",
|
||||
Requests: []labelBatchRequest{{
|
||||
Method: "DELETE",
|
||||
Path: path,
|
||||
}},
|
||||
})
|
||||
}
|
||||
if ctx.Arg("yes") != "true" {
|
||||
return fmt.Errorf("label delete is destructive; run with --dry-run first, then pass --yes to confirm")
|
||||
}
|
||||
env, err := ctx.CallAPI("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runBatchCreate(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
specs, err := parseLabelSpecs(ctx.Arg("labels"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requests := make([]labelBatchRequest, 0, len(specs))
|
||||
for _, spec := range specs {
|
||||
requests = append(requests, labelBatchRequest{
|
||||
Method: "POST",
|
||||
Path: labelPath(ctx),
|
||||
Body: map[string]interface{}{
|
||||
"name": spec.Name,
|
||||
"description": spec.Description,
|
||||
"color": spec.Color,
|
||||
},
|
||||
})
|
||||
}
|
||||
if ctx.Arg("dry-run") == "true" || ctx.Arg("yes") != "true" {
|
||||
return ctx.OutputData(labelBatchPreview{
|
||||
Repository: repositoryName(ctx),
|
||||
DryRun: true,
|
||||
Action: "batch_create_labels",
|
||||
Requests: requests,
|
||||
})
|
||||
}
|
||||
return runLabelBatchRequests(ctx, "batch_create_labels", requests)
|
||||
}
|
||||
|
||||
func runBatchDelete(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
ids, err := parseLabelIDList(ctx.Arg("ids"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requests := make([]labelBatchRequest, 0, len(ids))
|
||||
for _, id := range ids {
|
||||
requests = append(requests, labelBatchRequest{
|
||||
Method: "DELETE",
|
||||
Path: labelItemPath(ctx, id),
|
||||
})
|
||||
}
|
||||
if ctx.Arg("dry-run") == "true" || ctx.Arg("yes") != "true" {
|
||||
return ctx.OutputData(labelBatchPreview{
|
||||
Repository: repositoryName(ctx),
|
||||
DryRun: true,
|
||||
Action: "batch_delete_labels",
|
||||
Requests: requests,
|
||||
})
|
||||
}
|
||||
return runLabelBatchRequests(ctx, "batch_delete_labels", requests)
|
||||
}
|
||||
|
||||
func runLabelBatchRequests(ctx *common.RuntimeContext, action string, requests []labelBatchRequest) error {
|
||||
results := make([]labelBatchResult, 0, len(requests))
|
||||
succeeded := 0
|
||||
failed := 0
|
||||
for _, request := range requests {
|
||||
env, err := ctx.CallAPI(request.Method, request.Path, request.Body)
|
||||
result := labelBatchResult{Request: request}
|
||||
if err != nil {
|
||||
result.OK = false
|
||||
result.Error = err.Error()
|
||||
failed++
|
||||
} else {
|
||||
result.OK = env.OK
|
||||
result.Data = env.Data
|
||||
if env.OK {
|
||||
succeeded++
|
||||
} else {
|
||||
failed++
|
||||
}
|
||||
}
|
||||
results = append(results, result)
|
||||
}
|
||||
if err := ctx.OutputData(map[string]interface{}{
|
||||
"repository": repositoryName(ctx),
|
||||
"action": action,
|
||||
"count": len(requests),
|
||||
"succeeded": succeeded,
|
||||
"failed": failed,
|
||||
"results": results,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if failed > 0 {
|
||||
return fmt.Errorf("%d of %d label request(s) failed", failed, len(requests))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// fetchLabel looks up a single label by id from the list endpoint. GitLink does
|
||||
// not expose a single-label GET, so we page through the list and match by id.
|
||||
// A nil result (label not found) is not an error: the caller falls back to the
|
||||
|
|
@ -360,77 +202,6 @@ func labelItemPath(ctx *common.RuntimeContext, id string) string {
|
|||
return fmt.Sprintf("%s/%s", labelPath(ctx), url.PathEscape(id))
|
||||
}
|
||||
|
||||
func parseLabelSpecs(raw string) ([]labelSpec, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return nil, fmt.Errorf("--labels is required")
|
||||
}
|
||||
parts := strings.Split(raw, ";")
|
||||
specs := make([]labelSpec, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
part = strings.TrimSpace(part)
|
||||
if part == "" {
|
||||
continue
|
||||
}
|
||||
fields := strings.SplitN(part, ":", 3)
|
||||
name := strings.TrimSpace(fields[0])
|
||||
if name == "" {
|
||||
return nil, fmt.Errorf("invalid label spec %q: name is required", part)
|
||||
}
|
||||
color := defaultLabelColor
|
||||
if len(fields) > 1 && strings.TrimSpace(fields[1]) != "" {
|
||||
color = strings.TrimSpace(fields[1])
|
||||
}
|
||||
if err := validateColor(color); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
description := ""
|
||||
if len(fields) > 2 {
|
||||
description = strings.TrimSpace(fields[2])
|
||||
}
|
||||
specs = append(specs, labelSpec{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Color: color,
|
||||
})
|
||||
}
|
||||
if len(specs) == 0 {
|
||||
return nil, fmt.Errorf("--labels must contain at least one label spec")
|
||||
}
|
||||
return specs, nil
|
||||
}
|
||||
|
||||
func parseLabelIDList(raw string) ([]string, error) {
|
||||
if strings.TrimSpace(raw) == "" {
|
||||
return nil, fmt.Errorf("--ids is required")
|
||||
}
|
||||
parts := strings.Split(raw, ",")
|
||||
ids := make([]string, 0, len(parts))
|
||||
seen := map[string]bool{}
|
||||
for _, part := range parts {
|
||||
id := strings.TrimSpace(part)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
n, err := strconv.Atoi(id)
|
||||
if err != nil || n <= 0 {
|
||||
return nil, fmt.Errorf("invalid label id %q: use positive integer IDs", id)
|
||||
}
|
||||
if seen[id] {
|
||||
continue
|
||||
}
|
||||
seen[id] = true
|
||||
ids = append(ids, id)
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, fmt.Errorf("--ids must contain at least one label ID")
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func repositoryName(ctx *common.RuntimeContext) string {
|
||||
return fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo)
|
||||
}
|
||||
|
||||
func validateColor(color string) error {
|
||||
if !hexColorPattern.MatchString(color) {
|
||||
return fmt.Errorf("invalid --color value %q: use a hex color like #1E90FF or #abc", color)
|
||||
|
|
@ -473,3 +244,10 @@ func firstNonEmpty(values ...string) string {
|
|||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package license
|
|||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
|
|
@ -10,13 +11,14 @@ import (
|
|||
//
|
||||
// These shortcuts provide access to the GitLink license registry,
|
||||
// which lists all available open-source licenses supported by the platform.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List available licenses",
|
||||
Description: tr.T("cmd.license.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Filter licenses by name"},
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.license.name")},
|
||||
},
|
||||
Run: runList,
|
||||
},
|
||||
|
|
@ -34,3 +36,10 @@ func runList(ctx *common.RuntimeContext) error {
|
|||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
|
|
@ -21,11 +22,12 @@ var roleAliases = map[string]string{
|
|||
}
|
||||
|
||||
// Shortcuts returns repository member management shortcuts.
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List repository members",
|
||||
Description: tr.T("cmd.member.list.short"),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
@ -39,9 +41,9 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "add",
|
||||
Description: "Add a repository member by user ID",
|
||||
Description: tr.T("cmd.member.add.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "user-id", Short: "u", Usage: "GitLink user ID to add", Required: true},
|
||||
{Name: "user-id", Short: "u", Usage: tr.T("flag.member.user_id"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -60,19 +62,19 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "batch-add",
|
||||
Description: "Add multiple repository members by user IDs or a CSV file",
|
||||
Description: tr.T("cmd.member.batch-add.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "user-ids", Short: "u", Usage: "Comma-separated GitLink user IDs, for example: 101,102"},
|
||||
{Name: "from", Usage: "Read user IDs from a CSV file. Supports a user_id/id column or first column without header"},
|
||||
{Name: "dry-run", Usage: "Preview members that would be added without changing them", Bool: true, Default: "false"},
|
||||
{Name: "user-ids", Short: "u", Usage: tr.T("flag.member.user_ids")},
|
||||
{Name: "from", Usage: tr.T("flag.member.from")},
|
||||
{Name: "dry-run", Usage: tr.T("flag.member.dry_run"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runBatchAdd,
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Description: "Remove a repository member by user ID",
|
||||
Description: tr.T("cmd.member.remove.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "user-id", Short: "u", Usage: "GitLink user ID to remove", Required: true},
|
||||
{Name: "user-id", Short: "u", Usage: tr.T("flag.member.user_id_2"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -91,10 +93,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "role",
|
||||
Description: "Change a repository member role",
|
||||
Description: tr.T("cmd.member.role.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "user-id", Short: "u", Usage: "GitLink user ID to update", Required: true},
|
||||
{Name: "role", Short: "r", Usage: "Member role: Manager, Developer, or Reporter", Required: true},
|
||||
{Name: "user-id", Short: "u", Usage: tr.T("flag.member.user_id_2"), Required: true},
|
||||
{Name: "role", Short: "r", Usage: tr.T("flag.member.role"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -120,10 +122,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "invite-link",
|
||||
Description: "Get or create a repository invite link",
|
||||
Description: tr.T("cmd.member.invite-link.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "role", Short: "r", Usage: "Invite role: manager, developer, or reporter", Default: "developer"},
|
||||
{Name: "apply", Usage: "Whether joining by invite requires approval: true or false", Default: "true"},
|
||||
{Name: "role", Short: "r", Usage: tr.T("flag.member.role_2"), Default: "developer"},
|
||||
{Name: "apply", Usage: tr.T("flag.member.apply"), Default: "true"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -149,9 +151,9 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "invite-info",
|
||||
Description: "Show repository invite link information",
|
||||
Description: tr.T("cmd.member.invite-info.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "sign", Short: "s", Usage: "Invite link sign", Required: true},
|
||||
{Name: "sign", Short: "s", Usage: tr.T("flag.member.sign"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -172,9 +174,9 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "accept-invite",
|
||||
Description: "Accept a repository invite link",
|
||||
Description: tr.T("cmd.member.accept-invite.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "sign", Short: "s", Usage: "Invite link sign", Required: true},
|
||||
{Name: "sign", Short: "s", Usage: tr.T("flag.member.sign"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -476,3 +478,10 @@ func userIDColumn(header []string) int {
|
|||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,22 +5,24 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List milestones",
|
||||
Description: tr.T("cmd.milestone.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "keyword", Short: "k", Usage: "Search keyword"},
|
||||
{Name: "category", Short: "c", Usage: "Filter by category: opening, closed"},
|
||||
{Name: "only-name", Usage: "Return only milestone id and name: true or false"},
|
||||
{Name: "sort-by", Usage: "Sort field: created_on, updated_on, effective_date, issues_count, percent"},
|
||||
{Name: "sort-direction", Usage: "Sort direction: asc or desc"},
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
{Name: "keyword", Short: "k", Usage: tr.T("flag.search.keyword")},
|
||||
{Name: "category", Short: "c", Usage: tr.T("flag.milestone.category")},
|
||||
{Name: "only-name", Usage: tr.T("flag.milestone.only_name")},
|
||||
{Name: "sort-by", Usage: tr.T("flag.milestone.sort_by")},
|
||||
{Name: "sort-direction", Usage: tr.T("flag.milestone.sort_direction")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -43,11 +45,11 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "create",
|
||||
Description: "Create a milestone",
|
||||
Description: tr.T("cmd.milestone.create.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Milestone name", Required: true},
|
||||
{Name: "description", Short: "d", Usage: "Milestone description"},
|
||||
{Name: "due-date", Usage: "Due date in YYYY-MM-DD format"},
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.milestone.name"), Required: true},
|
||||
{Name: "description", Short: "d", Usage: tr.T("flag.milestone.description"), Required: true},
|
||||
{Name: "due-date", Usage: tr.T("flag.milestone.due_date"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -66,17 +68,17 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "view",
|
||||
Description: "View milestone details and linked issues",
|
||||
Description: tr.T("cmd.milestone.view.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Milestone ID", Required: true},
|
||||
{Name: "category", Short: "c", Usage: "Filter issues by category: all, opened, closed"},
|
||||
{Name: "author-id", Usage: "Filter issues by author ID"},
|
||||
{Name: "assigner-id", Usage: "Filter issues by assignee ID"},
|
||||
{Name: "issue-tag-ids", Usage: "Comma-separated issue tag IDs"},
|
||||
{Name: "sort-by", Usage: "Sort field: issues.created_on, issues.updated_on, issue_priorities.position"},
|
||||
{Name: "sort-direction", Usage: "Sort direction: asc or desc"},
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.milestone.id"), Required: true},
|
||||
{Name: "category", Short: "c", Usage: tr.T("flag.milestone.category_2")},
|
||||
{Name: "author-id", Usage: tr.T("flag.milestone.author_id")},
|
||||
{Name: "assigner-id", Usage: tr.T("flag.milestone.assigner_id")},
|
||||
{Name: "issue-tag-ids", Usage: tr.T("flag.milestone.issue_tag_ids")},
|
||||
{Name: "sort-by", Usage: tr.T("flag.milestone.sort_by_2")},
|
||||
{Name: "sort-direction", Usage: tr.T("flag.milestone.sort_direction")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -102,15 +104,14 @@ func Shortcuts() []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
newMilestoneReportShortcut(),
|
||||
{
|
||||
Name: "update",
|
||||
Description: "Update a milestone",
|
||||
Description: tr.T("cmd.milestone.update.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Milestone ID", Required: true},
|
||||
{Name: "name", Short: "n", Usage: "Milestone name"},
|
||||
{Name: "description", Short: "d", Usage: "Milestone description"},
|
||||
{Name: "due-date", Usage: "Due date in YYYY-MM-DD format"},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.milestone.id"), Required: true},
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.milestone.name")},
|
||||
{Name: "description", Short: "d", Usage: tr.T("flag.milestone.description")},
|
||||
{Name: "due-date", Usage: tr.T("flag.milestone.due_date")},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -133,9 +134,9 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Description: "Delete a milestone",
|
||||
Description: tr.T("cmd.milestone.delete.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Milestone ID", Required: true},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.milestone.id"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -152,8 +153,8 @@ func Shortcuts() []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
newStatusShortcut("close", "Close a milestone", "closed"),
|
||||
newStatusShortcut("reopen", "Reopen a milestone", "open"),
|
||||
newStatusShortcut(tr, "close", tr.T("cmd.milestone.close.short"), "closed"),
|
||||
newStatusShortcut(tr, "reopen", tr.T("cmd.milestone.reopen.short"), "open"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,7 +170,7 @@ func milestoneStatusPath(ctx *common.RuntimeContext, id string) string {
|
|||
return fmt.Sprintf("%s/milestones/%s/update_status", ctx.RepoPath(), url.PathEscape(id))
|
||||
}
|
||||
|
||||
func milestonePayload(ctx *common.RuntimeContext, requireName bool) (map[string]interface{}, error) {
|
||||
func milestonePayload(ctx *common.RuntimeContext, requireAll bool) (map[string]interface{}, error) {
|
||||
payload := map[string]interface{}{}
|
||||
if name := ctx.Arg("name"); name != "" {
|
||||
payload["name"] = name
|
||||
|
|
@ -181,9 +182,11 @@ func milestonePayload(ctx *common.RuntimeContext, requireName bool) (map[string]
|
|||
payload["effective_date"] = dueDate
|
||||
}
|
||||
|
||||
if requireName {
|
||||
if _, err := ctx.RequireArg("name"); err != nil {
|
||||
return nil, err
|
||||
if requireAll {
|
||||
for _, name := range []string{"name", "description", "due-date"} {
|
||||
if _, err := ctx.RequireArg(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
|
@ -193,12 +196,12 @@ func milestonePayload(ctx *common.RuntimeContext, requireName bool) (map[string]
|
|||
return payload, nil
|
||||
}
|
||||
|
||||
func newStatusShortcut(name, description, status string) *common.Shortcut {
|
||||
func newStatusShortcut(tr *i18n.Translator, name, description, status string) *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: name,
|
||||
Description: description,
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Milestone ID", Required: true},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.milestone.id"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -236,3 +239,10 @@ func normalizeCSV(value string) string {
|
|||
}
|
||||
return strings.Join(result, ",")
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,18 +7,20 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List platform pipelines",
|
||||
Description: tr.T("cmd.pipeline.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "owner-id", Usage: "Owner user or organization ID"},
|
||||
{Name: "page", Short: "p", Usage: "Page number", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Items per page", Default: "20"},
|
||||
{Name: "owner-id", Usage: tr.T("flag.pipeline.owner_id")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
q := pageLimitQuery(ctx)
|
||||
|
|
@ -32,8 +34,8 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "runs",
|
||||
Description: "List pipeline run records",
|
||||
Flags: runFilterFlags(),
|
||||
Description: tr.T("cmd.pipeline.runs.short"),
|
||||
Flags: runFilterFlags(tr),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
@ -47,9 +49,9 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "run",
|
||||
Description: "Run a pipeline workflow",
|
||||
Flags: append(runFilterFlags(),
|
||||
common.Flag{Name: "dry-run", Usage: "Preview the run request without starting a pipeline", Bool: true, Default: "false"},
|
||||
Description: tr.T("cmd.pipeline.run.short"),
|
||||
Flags: append(runFilterFlags(tr),
|
||||
common.Flag{Name: "dry-run", Usage: tr.T("flag.pipeline.dry_run"), Bool: true, Default: "false"},
|
||||
),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -76,9 +78,9 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "view",
|
||||
Description: "Show pipeline details",
|
||||
Description: tr.T("cmd.pipeline.view.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Pipeline ID", Required: true},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pipeline.id"), Required: true},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -97,10 +99,10 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Description: "Delete a pipeline",
|
||||
Description: tr.T("cmd.pipeline.delete.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Pipeline ID", Required: true},
|
||||
{Name: "dry-run", Usage: "Preview the delete request without changing pipeline state", Bool: true, Default: "false"},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pipeline.id"), Required: true},
|
||||
{Name: "dry-run", Usage: tr.T("flag.pipeline.dry_run_2"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -129,11 +131,11 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "save-yaml",
|
||||
Description: "Save a visual pipeline YAML graph",
|
||||
Description: tr.T("cmd.pipeline.save-yaml.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Pipeline ID", Required: true},
|
||||
{Name: "pipeline-json", Usage: "Pipeline graph JSON object or string", Required: true},
|
||||
{Name: "dry-run", Usage: "Preview the save request without changing pipeline state", Bool: true, Default: "false"},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pipeline.id"), Required: true},
|
||||
{Name: "pipeline-json", Usage: tr.T("flag.pipeline.pipeline_json"), Required: true},
|
||||
{Name: "dry-run", Usage: tr.T("flag.pipeline.dry_run_2"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -171,31 +173,31 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "enable",
|
||||
Description: "Enable a pipeline workflow",
|
||||
Flags: workflowStateFlags(true),
|
||||
Description: tr.T("cmd.pipeline.enable.short"),
|
||||
Flags: workflowStateFlags(tr, true),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
return runWorkflowState(ctx, "enable")
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "disable",
|
||||
Description: "Disable a pipeline workflow",
|
||||
Flags: workflowStateFlags(true),
|
||||
Description: tr.T("cmd.pipeline.disable.short"),
|
||||
Flags: workflowStateFlags(tr, true),
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
return runWorkflowState(ctx, "disable")
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "logs",
|
||||
Description: "Query pipeline run logs",
|
||||
Description: tr.T("cmd.pipeline.logs.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "run-id", Short: "r", Usage: "Pipeline run ID", Required: true},
|
||||
{Name: "id", Short: "i", Usage: "Pipeline ID", Required: true},
|
||||
{Name: "index", Usage: "Run index", Required: true},
|
||||
{Name: "job", Short: "j", Usage: "Job index", Default: "0"},
|
||||
{Name: "cursor", Usage: "Log cursor"},
|
||||
{Name: "step", Usage: "Log step", Default: "1"},
|
||||
{Name: "expanded", Usage: "Whether the log cursor is expanded", Bool: true, Default: "true"},
|
||||
{Name: "run-id", Short: "r", Usage: tr.T("flag.pipeline.run_id"), Required: true},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pipeline.id"), Required: true},
|
||||
{Name: "index", Usage: tr.T("flag.pipeline.index"), Required: true},
|
||||
{Name: "job", Short: "j", Usage: tr.T("flag.pipeline.job"), Default: "0"},
|
||||
{Name: "cursor", Usage: tr.T("flag.pipeline.cursor")},
|
||||
{Name: "step", Usage: tr.T("flag.pipeline.step"), Default: "1"},
|
||||
{Name: "expanded", Usage: tr.T("flag.pipeline.expanded"), Bool: true, Default: "true"},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -244,9 +246,9 @@ func Shortcuts() []*common.Shortcut {
|
|||
},
|
||||
{
|
||||
Name: "results",
|
||||
Description: "Show pipeline run report results",
|
||||
Description: tr.T("cmd.pipeline.results.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "run-id", Short: "r", Usage: "Pipeline run ID"},
|
||||
{Name: "run-id", Short: "r", Usage: tr.T("flag.pipeline.run_id")},
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
|
|
@ -268,10 +270,10 @@ func pipelineV1RepoPath(ctx *common.RuntimeContext) string {
|
|||
return "/v1" + ctx.RepoPath()
|
||||
}
|
||||
|
||||
func runFilterFlags() []common.Flag {
|
||||
func runFilterFlags(tr *i18n.Translator) []common.Flag {
|
||||
return []common.Flag{
|
||||
{Name: "ref", Short: "r", Usage: "Branch, tag, or commit SHA"},
|
||||
{Name: "workflow", Short: "w", Usage: "Workflow file name"},
|
||||
{Name: "ref", Short: "r", Usage: tr.T("flag.pipeline.ref")},
|
||||
{Name: "workflow", Short: "w", Usage: tr.T("flag.pipeline.workflow")},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -282,13 +284,13 @@ func runFilterQuery(ctx *common.RuntimeContext) url.Values {
|
|||
return q
|
||||
}
|
||||
|
||||
func workflowStateFlags(includeDryRun bool) []common.Flag {
|
||||
func workflowStateFlags(tr *i18n.Translator, includeDryRun bool) []common.Flag {
|
||||
flags := []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: "Pipeline ID", Required: true},
|
||||
{Name: "workflow", Short: "w", Usage: "Workflow file name", Required: true},
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pipeline.id"), Required: true},
|
||||
{Name: "workflow", Short: "w", Usage: tr.T("flag.pipeline.workflow"), Required: true},
|
||||
}
|
||||
if includeDryRun {
|
||||
flags = append(flags, common.Flag{Name: "dry-run", Usage: "Preview the request without changing pipeline state", Bool: true, Default: "false"})
|
||||
flags = append(flags, common.Flag{Name: "dry-run", Usage: tr.T("flag.pipeline.dry_run_2"), Bool: true, Default: "false"})
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
|
@ -387,3 +389,10 @@ func requiredNonNegativeIntWithDefault(ctx *common.RuntimeContext, flagName stri
|
|||
}
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/attachment"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/branch"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/ci"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
|
|
@ -37,55 +36,53 @@ func RegisterAll(root *cobra.Command, translators ...*i18n.Translator) {
|
|||
tr = translators[0]
|
||||
}
|
||||
groups := map[string][]*common.Shortcut{
|
||||
"repo": repo.Shortcuts(tr),
|
||||
"attachment": attachment.Shortcuts(tr),
|
||||
"issue": issue.Shortcuts(tr),
|
||||
"label": label.Shortcuts(),
|
||||
"license": license.Shortcuts(),
|
||||
"member": member.Shortcuts(),
|
||||
"milestone": milestone.Shortcuts(),
|
||||
"pipeline": pipeline.Shortcuts(),
|
||||
"pr": pr.Shortcuts(tr),
|
||||
"profile": profile.Shortcuts(tr),
|
||||
"release": release.Shortcuts(tr),
|
||||
"branch": branch.Shortcuts(tr),
|
||||
"org": org.Shortcuts(tr),
|
||||
"user": user.Shortcuts(tr),
|
||||
"search": search.Shortcuts(tr),
|
||||
"ci": ci.Shortcuts(tr),
|
||||
"compare": compare.Shortcuts(),
|
||||
"dataset": dataset.Shortcuts(tr),
|
||||
"webhook": webhook.Shortcuts(tr),
|
||||
"wiki": wiki.Shortcuts(),
|
||||
"health": health.Shortcuts(tr),
|
||||
"ignore": ignore.Shortcuts(),
|
||||
"workflow": workflow.Shortcuts(),
|
||||
"repo": repo.Shortcuts(tr),
|
||||
"issue": issue.Shortcuts(tr),
|
||||
"label": label.Shortcuts(tr),
|
||||
"license": license.Shortcuts(tr),
|
||||
"member": member.Shortcuts(tr),
|
||||
"milestone": milestone.Shortcuts(tr),
|
||||
"pipeline": pipeline.Shortcuts(tr),
|
||||
"pr": pr.Shortcuts(tr),
|
||||
"profile": profile.Shortcuts(tr),
|
||||
"release": release.Shortcuts(tr),
|
||||
"branch": branch.Shortcuts(tr),
|
||||
"org": org.Shortcuts(tr),
|
||||
"user": user.Shortcuts(tr),
|
||||
"search": search.Shortcuts(tr),
|
||||
"ci": ci.Shortcuts(tr),
|
||||
"compare": compare.Shortcuts(tr),
|
||||
"dataset": dataset.Shortcuts(tr),
|
||||
"webhook": webhook.Shortcuts(tr),
|
||||
"wiki": wiki.Shortcuts(tr),
|
||||
"health": health.Shortcuts(tr),
|
||||
"ignore": ignore.Shortcuts(tr),
|
||||
"workflow": workflow.Shortcuts(tr),
|
||||
}
|
||||
|
||||
descriptions := map[string]string{
|
||||
"repo": tr.T("cmd.repo.short"),
|
||||
"attachment": tr.T("cmd.attachment.short"),
|
||||
"issue": tr.T("cmd.issue.short"),
|
||||
"label": "Issue label operations",
|
||||
"license": "License operations",
|
||||
"member": "Repository member operations",
|
||||
"milestone": "Milestone operations",
|
||||
"pipeline": "Pipeline operations",
|
||||
"pr": tr.T("cmd.pr.short"),
|
||||
"profile": tr.T("cmd.profile.short"),
|
||||
"release": tr.T("cmd.release.short"),
|
||||
"branch": tr.T("cmd.branch.short"),
|
||||
"org": tr.T("cmd.org.short"),
|
||||
"user": tr.T("cmd.user.short"),
|
||||
"search": tr.T("cmd.search.short"),
|
||||
"ci": tr.T("cmd.ci.short"),
|
||||
"compare": "Compare branches, tags, or commits",
|
||||
"dataset": tr.T("cmd.dataset.short"),
|
||||
"webhook": tr.T("cmd.webhook.short"),
|
||||
"wiki": "Wiki page management",
|
||||
"health": "Project health data collection",
|
||||
"ignore": tr.T("cmd.ignore.short"),
|
||||
"workflow": "AI agent workflow analysis",
|
||||
"repo": tr.T("cmd.repo.short"),
|
||||
"issue": tr.T("cmd.issue.short"),
|
||||
"label": tr.T("cmd.label.short"),
|
||||
"license": tr.T("cmd.license.short"),
|
||||
"member": tr.T("cmd.member.short"),
|
||||
"milestone": tr.T("cmd.milestone.short"),
|
||||
"pipeline": tr.T("cmd.pipeline.short"),
|
||||
"pr": tr.T("cmd.pr.short"),
|
||||
"profile": tr.T("cmd.profile.short"),
|
||||
"release": tr.T("cmd.release.short"),
|
||||
"branch": tr.T("cmd.branch.short"),
|
||||
"org": tr.T("cmd.org.short"),
|
||||
"user": tr.T("cmd.user.short"),
|
||||
"search": tr.T("cmd.search.short"),
|
||||
"ci": tr.T("cmd.ci.short"),
|
||||
"compare": tr.T("cmd.compare.short"),
|
||||
"dataset": tr.T("cmd.dataset.short"),
|
||||
"webhook": tr.T("cmd.webhook.short"),
|
||||
"wiki": tr.T("cmd.wiki.short"),
|
||||
"health": tr.T("cmd.health.short"),
|
||||
"ignore": tr.T("cmd.ignore.short"),
|
||||
"workflow": tr.T("cmd.workflow.short"),
|
||||
}
|
||||
|
||||
for name, shortcuts := range groups {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package shortcuts
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
|
@ -60,6 +61,9 @@ func TestRegisterAllGroupDescriptions(t *testing.T) {
|
|||
if cmd.Short == "" {
|
||||
t.Fatal("Short description is empty")
|
||||
}
|
||||
if strings.HasPrefix(cmd.Short, "cmd.") {
|
||||
t.Fatalf("Short description leaks raw i18n key: %q", cmd.Short)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,301 +2,208 @@ package wiki
|
|||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/config"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
const wikiBaseURL = "https://gateway.gitlink.org.cn/api"
|
||||
// switchToGateway overrides the client base URL with the gateway URL from config.
|
||||
func switchToGateway(ctx *common.RuntimeContext) error {
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cfg.GatewayURL == "" {
|
||||
cfg.GatewayURL = config.DefaultGatewayURL
|
||||
}
|
||||
ctx.Client.BaseURL = cfg.GatewayURL
|
||||
return nil
|
||||
}
|
||||
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
// gatewayFlag returns the common --gateway flag definition.
|
||||
func gatewayFlag(tr *i18n.Translator) common.Flag {
|
||||
return common.Flag{Name: "gateway", Short: "g", Usage: tr.T("flag.wiki.gateway"), Bool: true}
|
||||
}
|
||||
|
||||
// Shortcuts returns all wiki shortcuts.
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
{
|
||||
Name: "list",
|
||||
Description: "List wiki pages",
|
||||
Description: tr.T("cmd.wiki.list.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id"), Required: true},
|
||||
gatewayFlag(tr),
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
if ctx.Arg("gateway") == "true" {
|
||||
if err := switchToGateway(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
projectID, err := fetchProjectID(ctx)
|
||||
if err != nil {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("owner", ctx.Owner)
|
||||
q.Set("repo", ctx.Repo)
|
||||
q.Set("projectId", fmt.Sprintf("%d", projectID))
|
||||
return callWikiAPI(ctx, "GET", "/wiki/open/wikiPages", nil, q)
|
||||
q.Set("projectId", ctx.Arg("project-id"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/wiki/open/wikiPages", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "view",
|
||||
Description: "View a wiki page",
|
||||
Description: tr.T("cmd.wiki.view.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Wiki page name", Required: true},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id"), Required: true},
|
||||
{Name: "page-name", Short: "n", Usage: tr.T("flag.wiki.page_name"), Required: true},
|
||||
gatewayFlag(tr),
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if ctx.Arg("gateway") == "true" {
|
||||
if err := switchToGateway(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := ctx.RequireArg("name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectID, err := fetchProjectID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("owner", ctx.Owner)
|
||||
q.Set("repo", ctx.Repo)
|
||||
q.Set("projectId", fmt.Sprintf("%d", projectID))
|
||||
q.Set("pageName", name)
|
||||
return callWikiAPI(ctx, "GET", "/wiki/open/getWiki", nil, q)
|
||||
q.Set("projectId", ctx.Arg("project-id"))
|
||||
q.Set("pageName", ctx.Arg("page-name"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/wiki/open/getWiki", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Description: "Create a wiki page",
|
||||
Description: tr.T("cmd.wiki.create.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Wiki page name", Required: true},
|
||||
{Name: "content", Short: "c", Usage: "Page content (will be base64 encoded)", Required: true},
|
||||
{Name: "message", Short: "m", Usage: "Commit message"},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id"), Required: true},
|
||||
{Name: "page-name", Short: "n", Usage: tr.T("flag.wiki.page_name"), Required: true},
|
||||
{Name: "title", Short: "t", Usage: tr.T("flag.wiki.title"), Required: true},
|
||||
{Name: "content", Short: "c", Usage: tr.T("flag.wiki.content"), Required: true},
|
||||
{Name: "message", Short: "m", Usage: tr.T("flag.wiki.message")},
|
||||
gatewayFlag(tr),
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if ctx.Arg("gateway") == "true" {
|
||||
if err := switchToGateway(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := ctx.RequireArg("name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := ctx.RequireArg("content")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectID, err := fetchProjectID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
content := ctx.Arg("content")
|
||||
payload := map[string]interface{}{
|
||||
"owner": ctx.Owner,
|
||||
"repo": ctx.Repo,
|
||||
"projectId": projectID,
|
||||
"pageName": name,
|
||||
"title": name,
|
||||
"message": ctx.Arg("message"),
|
||||
"projectId": ctx.Arg("project-id"),
|
||||
"pageName": ctx.Arg("page-name"),
|
||||
"title": ctx.Arg("title"),
|
||||
"content_base64": base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
"message": ctx.Arg("message"),
|
||||
}
|
||||
return callWikiAPI(ctx, "POST", "/wiki/open/createWiki", body, nil)
|
||||
env, err := ctx.CallAPI("POST", "/wiki/open/createWiki", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "update",
|
||||
Description: "Update a wiki page",
|
||||
Description: tr.T("cmd.wiki.update.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Wiki page name", Required: true},
|
||||
{Name: "content", Short: "c", Usage: "New page content (will be base64 encoded)", Required: true},
|
||||
{Name: "message", Short: "m", Usage: "Commit message"},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id"), Required: true},
|
||||
{Name: "page-name", Short: "n", Usage: tr.T("flag.wiki.page_name"), Required: true},
|
||||
{Name: "title", Short: "t", Usage: tr.T("flag.wiki.title"), Required: true},
|
||||
{Name: "content", Short: "c", Usage: tr.T("flag.wiki.content")},
|
||||
{Name: "message", Short: "m", Usage: tr.T("flag.wiki.message")},
|
||||
gatewayFlag(tr),
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if ctx.Arg("gateway") == "true" {
|
||||
if err := switchToGateway(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := ctx.RequireArg("name")
|
||||
title := ctx.Arg("title")
|
||||
if title == "" {
|
||||
return fmt.Errorf("--title is required")
|
||||
}
|
||||
content := ctx.Arg("content")
|
||||
payload := map[string]interface{}{
|
||||
"owner": ctx.Owner,
|
||||
"repo": ctx.Repo,
|
||||
"projectId": ctx.Arg("project-id"),
|
||||
"pageName": ctx.Arg("page-name"),
|
||||
"title": title,
|
||||
"message": ctx.Arg("message"),
|
||||
}
|
||||
if content != "" {
|
||||
payload["content_base64"] = base64.StdEncoding.EncodeToString([]byte(content))
|
||||
}
|
||||
env, err := ctx.CallAPI("PUT", "/wiki/open/updateWiki", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content, err := ctx.RequireArg("content")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectID, err := fetchProjectID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"owner": ctx.Owner,
|
||||
"repo": ctx.Repo,
|
||||
"projectId": projectID,
|
||||
"pageName": name,
|
||||
"title": name,
|
||||
"message": ctx.Arg("message"),
|
||||
"content_base64": base64.StdEncoding.EncodeToString([]byte(content)),
|
||||
}
|
||||
return callWikiAPI(ctx, "PUT", "/wiki/open/updateWiki", body, nil)
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Description: "Delete a wiki page and remove it from sidebar",
|
||||
Description: tr.T("cmd.wiki.delete.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: "Wiki page name", Required: true},
|
||||
{Name: "project-id", Usage: tr.T("flag.wiki.project_id"), Required: true},
|
||||
{Name: "page-name", Short: "n", Usage: tr.T("flag.wiki.page_name"), Required: true},
|
||||
gatewayFlag(tr),
|
||||
},
|
||||
Run: func(ctx *common.RuntimeContext) error {
|
||||
if ctx.Arg("gateway") == "true" {
|
||||
if err := switchToGateway(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := ctx.RequireArg("name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
projectID, err := fetchProjectID(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 1: Delete the wiki page
|
||||
body := map[string]interface{}{
|
||||
payload := map[string]interface{}{
|
||||
"owner": ctx.Owner,
|
||||
"repo": ctx.Repo,
|
||||
"projectId": projectID,
|
||||
"pageName": name,
|
||||
"projectId": ctx.Arg("project-id"),
|
||||
"pageName": ctx.Arg("page-name"),
|
||||
}
|
||||
if err := callWikiAPISilent(ctx, "DELETE", "/wiki/open/deleteWiki", body, nil); err != nil {
|
||||
env, err := ctx.CallAPI("DELETE", "/wiki/open/deleteWiki", payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Step 2: Wait for GitLink async sidebar rebuild, then clean up
|
||||
time.Sleep(2 * time.Second)
|
||||
cleanSidebar(ctx, projectID, name)
|
||||
|
||||
fmt.Printf("Wiki page %q deleted successfully.\n", name)
|
||||
return nil
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// callWikiAPI sends a request to the wiki gateway.
|
||||
// It switches the client BaseURL to the wiki gateway for the duration of the call,
|
||||
// but skips the switch during tests (local httptest server).
|
||||
func callWikiAPI(ctx *common.RuntimeContext, method, path string, body interface{}, query url.Values) error {
|
||||
origBase := ctx.Client.BaseURL
|
||||
if !strings.HasPrefix(origBase, "http://127.0.0.1") && !strings.HasPrefix(origBase, "http://localhost") {
|
||||
ctx.Client.BaseURL = wikiBaseURL
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
defer func() { ctx.Client.BaseURL = origBase }()
|
||||
|
||||
env, err := ctx.Client.DoRaw(method, path, body, query)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
// fetchProjectID resolves the numeric project ID from the repo info API.
|
||||
func fetchProjectID(ctx *common.RuntimeContext) (int64, error) {
|
||||
env, err := ctx.CallAPI("GET", ctx.RepoPath(), nil)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get project info: %w", err)
|
||||
}
|
||||
data, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("unexpected project info response")
|
||||
}
|
||||
for _, key := range []string{"project_id", "repo_id", "id"} {
|
||||
if id, ok := data[key].(float64); ok {
|
||||
return int64(id), nil
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("project id not found in response")
|
||||
}
|
||||
|
||||
// callWikiAPISilent is like callWikiAPI but does not print output.
|
||||
func callWikiAPISilent(ctx *common.RuntimeContext, method, path string, body interface{}, query url.Values) error {
|
||||
origBase := ctx.Client.BaseURL
|
||||
if !strings.HasPrefix(origBase, "http://127.0.0.1") && !strings.HasPrefix(origBase, "http://localhost") {
|
||||
ctx.Client.BaseURL = wikiBaseURL
|
||||
}
|
||||
defer func() { ctx.Client.BaseURL = origBase }()
|
||||
|
||||
_, err := ctx.Client.DoRaw(method, path, body, query)
|
||||
return err
|
||||
}
|
||||
|
||||
const sidebarPageName = "_Sidebar" // GitLink uses capital S for the sidebar page
|
||||
|
||||
// cleanSidebar fetches the wiki sidebar, removes the deleted page link, and updates it.
|
||||
func cleanSidebar(ctx *common.RuntimeContext, projectID int64, pageName string) {
|
||||
// Fetch sidebar
|
||||
q := url.Values{}
|
||||
q.Set("owner", ctx.Owner)
|
||||
q.Set("repo", ctx.Repo)
|
||||
q.Set("projectId", fmt.Sprintf("%d", projectID))
|
||||
q.Set("pageName", sidebarPageName)
|
||||
|
||||
origBase := ctx.Client.BaseURL
|
||||
if !strings.HasPrefix(origBase, "http://127.0.0.1") && !strings.HasPrefix(origBase, "http://localhost") {
|
||||
ctx.Client.BaseURL = wikiBaseURL
|
||||
}
|
||||
defer func() { ctx.Client.BaseURL = origBase }()
|
||||
|
||||
env, err := ctx.Client.DoRaw("GET", "/wiki/open/getWiki", nil, q)
|
||||
if err != nil {
|
||||
return // sidebar might not exist, silently skip
|
||||
}
|
||||
|
||||
// Extract content_base64 from response.
|
||||
// DoRaw auto-parses JSON, so env.Data is a map with "data" as either
|
||||
// a nested dict (already parsed) or a JSON string (needs parsing).
|
||||
outer, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var inner map[string]interface{}
|
||||
switch v := outer["data"].(type) {
|
||||
case map[string]interface{}:
|
||||
inner = v
|
||||
case string:
|
||||
if err := json.Unmarshal([]byte(v), &inner); err != nil {
|
||||
return
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
contentB64, ok := inner["content_base64"].(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
contentBytes, err := base64.StdEncoding.DecodeString(contentB64)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
sidebar := string(contentBytes)
|
||||
|
||||
// Remove the line containing [[pageName]]
|
||||
target := "[[" + pageName + "]]"
|
||||
lines := strings.Split(sidebar, "\n")
|
||||
var newLines []string
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if trimmed != target {
|
||||
newLines = append(newLines, line)
|
||||
}
|
||||
}
|
||||
newSidebar := strings.Join(newLines, "\n")
|
||||
|
||||
// No change needed
|
||||
if newSidebar == sidebar {
|
||||
return
|
||||
}
|
||||
|
||||
// Update sidebar
|
||||
body := map[string]interface{}{
|
||||
"owner": ctx.Owner,
|
||||
"repo": ctx.Repo,
|
||||
"projectId": projectID,
|
||||
"pageName": sidebarPageName,
|
||||
"title": sidebarPageName,
|
||||
"message": "Remove deleted page " + pageName + " from sidebar",
|
||||
"content_base64": base64.StdEncoding.EncodeToString([]byte(newSidebar)),
|
||||
}
|
||||
ctx.Client.DoRaw("PUT", "/wiki/open/updateWiki", body, nil)
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/cmd/cmdutil"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
|
|
@ -82,18 +83,18 @@ type PRSummaryResult struct {
|
|||
Source string `json:"source"`
|
||||
}
|
||||
|
||||
func newPRSummaryShortcut() *common.Shortcut {
|
||||
func newPRSummaryShortcut(tr *i18n.Translator) *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: "pr-summary",
|
||||
Description: "Generate a read-only pull request review summary",
|
||||
Description: tr.T("cmd.workflow.pr-summary.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "from", Usage: "Read PR summary input from a JSON file"},
|
||||
{Name: "number", Short: "n", Usage: "Pull request number for remote read-only analysis"},
|
||||
{Name: "include-commits", Usage: "Fetch commits in remote mode", Bool: true, Default: "true"},
|
||||
{Name: "include-files", Usage: "Fetch changed files in remote mode", Bool: true, Default: "true"},
|
||||
{Name: "max-files", Usage: "Maximum changed files to analyze", Default: "100"},
|
||||
{Name: "max-commits", Usage: "Maximum commits to analyze", Default: "100"},
|
||||
{Name: "lang", Usage: "Output language: en or zh-CN", Default: langEN},
|
||||
{Name: "from", Usage: tr.T("flag.workflow.pr_summary.from")},
|
||||
{Name: "number", Short: "n", Usage: tr.T("flag.workflow.pr_summary.number")},
|
||||
{Name: "include-commits", Usage: tr.T("flag.workflow.pr_summary.include_commits"), Bool: true, Default: "true"},
|
||||
{Name: "include-files", Usage: tr.T("flag.workflow.pr_summary.include_files"), Bool: true, Default: "true"},
|
||||
{Name: "max-files", Usage: tr.T("flag.workflow.pr_summary.max_files"), Default: "100"},
|
||||
{Name: "max-commits", Usage: tr.T("flag.workflow.pr_summary.max_commits"), Default: "100"},
|
||||
{Name: "lang", Usage: tr.T("flag.workflow.pr_summary.lang"), Default: langEN},
|
||||
},
|
||||
Run: runPRSummary,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/cmd/cmdutil"
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
|
|
@ -48,19 +49,19 @@ type RepoPRSummary struct {
|
|||
ReviewFocus []string `json:"review_focus"`
|
||||
}
|
||||
|
||||
func newRepoReportShortcut() *common.Shortcut {
|
||||
func newRepoReportShortcut(tr *i18n.Translator) *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: "repo-report",
|
||||
Description: "Generate a read-only repository workflow report",
|
||||
Description: tr.T("cmd.workflow.repo-report.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "from", Usage: "Read repository report input from a JSON file"},
|
||||
{Name: "issue-limit", Usage: "Maximum issues to fetch and analyze", Default: "20"},
|
||||
{Name: "pr-limit", Usage: "Maximum pull requests to fetch and summarize", Default: "10"},
|
||||
{Name: "stale-days", Usage: "Days before an issue or PR is considered stale", Default: "30"},
|
||||
{Name: "include-issues", Usage: "Include issue triage summary", Bool: true, Default: "true"},
|
||||
{Name: "include-prs", Usage: "Include pull request summary", Bool: true, Default: "true"},
|
||||
{Name: "include-health", Usage: "Include repository health summary", Bool: true, Default: "true"},
|
||||
{Name: "lang", Usage: "Output language: en or zh-CN", Default: langEN},
|
||||
{Name: "from", Usage: tr.T("flag.workflow.repo_report.from")},
|
||||
{Name: "issue-limit", Usage: tr.T("flag.workflow.repo_report.issue_limit"), Default: "20"},
|
||||
{Name: "pr-limit", Usage: tr.T("flag.workflow.repo_report.pr_limit"), Default: "10"},
|
||||
{Name: "stale-days", Usage: tr.T("flag.workflow.repo_report.stale_days"), Default: "30"},
|
||||
{Name: "include-issues", Usage: tr.T("flag.workflow.repo_report.include_issues"), Bool: true, Default: "true"},
|
||||
{Name: "include-prs", Usage: tr.T("flag.workflow.repo_report.include_prs"), Bool: true, Default: "true"},
|
||||
{Name: "include-health", Usage: tr.T("flag.workflow.repo_report.include_health"), Bool: true, Default: "true"},
|
||||
{Name: "lang", Usage: tr.T("flag.workflow.repo_report.lang"), Default: langEN},
|
||||
},
|
||||
Run: runRepoReport,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/i18n"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
|
|
@ -19,63 +20,63 @@ type TriageReport struct {
|
|||
Results []TriageResult `json:"results"`
|
||||
}
|
||||
|
||||
func Shortcuts() []*common.Shortcut {
|
||||
func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
||||
tr := shortcutTranslator(translators...)
|
||||
return []*common.Shortcut{
|
||||
newTriageShortcut(),
|
||||
newHealthShortcut(),
|
||||
newPRSummaryShortcut(),
|
||||
newRepoReportShortcut(),
|
||||
newReleaseNotesShortcut(),
|
||||
newTriageShortcut(tr),
|
||||
newHealthShortcut(tr),
|
||||
newPRSummaryShortcut(tr),
|
||||
newRepoReportShortcut(tr),
|
||||
}
|
||||
}
|
||||
|
||||
func newTriageShortcut() *common.Shortcut {
|
||||
func newTriageShortcut(tr *i18n.Translator) *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: "triage",
|
||||
Description: "Analyze issues with local workflow triage rules",
|
||||
Description: tr.T("cmd.workflow.triage.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "from", Usage: "Read issue inputs from a JSON file. Supports a single issue, an array, or an object with an issues field"},
|
||||
{Name: "title", Short: "t", Usage: "Issue title for single-issue local analysis"},
|
||||
{Name: "body", Short: "b", Usage: "Issue body for single-issue local analysis"},
|
||||
{Name: "number", Short: "n", Usage: "Issue number for single-issue local analysis"},
|
||||
{Name: "author", Usage: "Issue author for single-issue local analysis"},
|
||||
{Name: "url", Usage: "Issue URL for single-issue local analysis"},
|
||||
{Name: "labels", Usage: "Comma-separated labels for single-issue local analysis"},
|
||||
{Name: "state", Short: "s", Usage: "Filter or assign issue state", Default: "open"},
|
||||
{Name: "page", Short: "p", Usage: "API page number for remote triage", Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: "Maximum issues to analyze", Default: "30"},
|
||||
{Name: "since", Usage: "Optional remote issue filter for updated time"},
|
||||
{Name: "dry-run", Usage: "Preview workflow recommendations without remote writes", Bool: true, Default: "true"},
|
||||
{Name: "lang", Usage: "Output language: en or zh-CN", Default: langEN},
|
||||
{Name: "from", Usage: tr.T("flag.workflow.from")},
|
||||
{Name: "title", Short: "t", Usage: tr.T("flag.workflow.title")},
|
||||
{Name: "body", Short: "b", Usage: tr.T("flag.workflow.body")},
|
||||
{Name: "number", Short: "n", Usage: tr.T("flag.workflow.number")},
|
||||
{Name: "author", Usage: tr.T("flag.workflow.author")},
|
||||
{Name: "url", Usage: tr.T("flag.workflow.url")},
|
||||
{Name: "labels", Usage: tr.T("flag.workflow.labels")},
|
||||
{Name: "state", Short: "s", Usage: tr.T("flag.workflow.state"), Default: "open"},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.workflow.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.workflow.limit"), Default: "30"},
|
||||
{Name: "since", Usage: tr.T("flag.workflow.since")},
|
||||
{Name: "dry-run", Usage: tr.T("flag.workflow.dry_run"), Bool: true, Default: "true"},
|
||||
{Name: "lang", Usage: tr.T("flag.workflow.lang"), Default: langEN},
|
||||
},
|
||||
Run: runTriage,
|
||||
}
|
||||
}
|
||||
|
||||
func newHealthShortcut() *common.Shortcut {
|
||||
func newHealthShortcut(tr *i18n.Translator) *common.Shortcut {
|
||||
return &common.Shortcut{
|
||||
Name: "health",
|
||||
Description: "Score repository health with local workflow rules",
|
||||
Description: tr.T("cmd.workflow.health.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "from", Usage: "Read health input from a JSON file"},
|
||||
{Name: "repository", Usage: "Repository name, for example owner/repo"},
|
||||
{Name: "open-issues", Usage: "Open issue count", Default: "0"},
|
||||
{Name: "open-prs", Usage: "Open pull request count", Default: "0"},
|
||||
{Name: "stale-issues", Usage: "Stale issue count", Default: "0"},
|
||||
{Name: "stale-prs", Usage: "Stale pull request count", Default: "0"},
|
||||
{Name: "recent-activity-known", Usage: "Whether recent activity is known", Bool: true, Default: "false"},
|
||||
{Name: "recent-activity-days", Usage: "Days since recent activity", Default: "0"},
|
||||
{Name: "release-known", Usage: "Whether release status is known", Bool: true, Default: "false"},
|
||||
{Name: "has-recent-release", Usage: "Whether a recent release exists", Bool: true, Default: "false"},
|
||||
{Name: "ci-known", Usage: "Whether CI status is known", Bool: true, Default: "false"},
|
||||
{Name: "ci-passing", Usage: "Whether CI is passing", Bool: true, Default: "false"},
|
||||
{Name: "has-readme", Usage: "Whether README exists", Bool: true, Default: "false"},
|
||||
{Name: "has-license", Usage: "Whether LICENSE exists", Bool: true, Default: "false"},
|
||||
{Name: "has-contributing", Usage: "Whether CONTRIBUTING exists", Bool: true, Default: "false"},
|
||||
{Name: "agent-readiness-known", Usage: "Whether agent readiness score is known", Bool: true, Default: "false"},
|
||||
{Name: "agent-readiness-score", Usage: "Agent readiness score from 0 to 10", Default: "0"},
|
||||
{Name: "stale-days", Usage: "Days before an issue or PR is considered stale", Default: "30"},
|
||||
{Name: "lang", Usage: "Output language: en or zh-CN", Default: langEN},
|
||||
{Name: "from", Usage: tr.T("flag.workflow.from_2")},
|
||||
{Name: "repository", Usage: tr.T("flag.workflow.repository")},
|
||||
{Name: "open-issues", Usage: tr.T("flag.workflow.open_issues"), Default: "0"},
|
||||
{Name: "open-prs", Usage: tr.T("flag.workflow.open_prs"), Default: "0"},
|
||||
{Name: "stale-issues", Usage: tr.T("flag.workflow.stale_issues"), Default: "0"},
|
||||
{Name: "stale-prs", Usage: tr.T("flag.workflow.stale_prs"), Default: "0"},
|
||||
{Name: "recent-activity-known", Usage: tr.T("flag.workflow.recent_activity_known"), Bool: true, Default: "false"},
|
||||
{Name: "recent-activity-days", Usage: tr.T("flag.workflow.recent_activity_days"), Default: "0"},
|
||||
{Name: "release-known", Usage: tr.T("flag.workflow.release_known"), Bool: true, Default: "false"},
|
||||
{Name: "has-recent-release", Usage: tr.T("flag.workflow.has_recent_release"), Bool: true, Default: "false"},
|
||||
{Name: "ci-known", Usage: tr.T("flag.workflow.ci_known"), Bool: true, Default: "false"},
|
||||
{Name: "ci-passing", Usage: tr.T("flag.workflow.ci_passing"), Bool: true, Default: "false"},
|
||||
{Name: "has-readme", Usage: tr.T("flag.workflow.has_readme"), Bool: true, Default: "false"},
|
||||
{Name: "has-license", Usage: tr.T("flag.workflow.has_license"), Bool: true, Default: "false"},
|
||||
{Name: "has-contributing", Usage: tr.T("flag.workflow.has_contributing"), Bool: true, Default: "false"},
|
||||
{Name: "agent-readiness-known", Usage: tr.T("flag.workflow.agent_readiness_known"), Bool: true, Default: "false"},
|
||||
{Name: "agent-readiness-score", Usage: tr.T("flag.workflow.agent_readiness_score"), Default: "0"},
|
||||
{Name: "stale-days", Usage: tr.T("flag.workflow.stale_days"), Default: "30"},
|
||||
{Name: "lang", Usage: tr.T("flag.workflow.lang"), Default: langEN},
|
||||
},
|
||||
Run: runHealth,
|
||||
}
|
||||
|
|
@ -385,3 +386,10 @@ func mustParseInt(value string, defaultValue int) int {
|
|||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
||||
if len(translators) > 0 && translators[0] != nil {
|
||||
return translators[0]
|
||||
}
|
||||
return i18n.Default()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue