feat: 添加 Gitea PR 评审 Action 的初始实现
This commit is contained in:
149
.gitignore
vendored
Normal file
149
.gitignore
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
.output
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Sveltekit cache directory
|
||||||
|
.svelte-kit/
|
||||||
|
|
||||||
|
# vitepress build output
|
||||||
|
**/.vitepress/dist
|
||||||
|
|
||||||
|
# vitepress cache directory
|
||||||
|
**/.vitepress/cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# Firebase cache directory
|
||||||
|
.firebase/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# IDE and editors
|
||||||
|
.vscode/
|
||||||
|
.cursor/
|
||||||
|
.trae
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
.pnpm-store
|
||||||
|
|
||||||
|
# yarn v3
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# Vite files
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
|
.vite/
|
||||||
65
README.md
Normal file
65
README.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Gitea PR 评审 Action
|
||||||
|
|
||||||
|
这个 Gitea Action 允许你在 Gitea 的 Pull Request 中添加或更新代码评审(Review)评论。它专为自动化代码审阅工具设计,支持精准的代码行评论。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- **零配置启动**:自动从运行环境获取 Gitea 地址、仓库名、PR 编号和提交 ID。
|
||||||
|
- **行级评论**:支持在代码文件的特定行添加评审意见。
|
||||||
|
- **评审总结**:支持提交整体的评审状态(批准、请求修改或仅评论)。
|
||||||
|
- **智能更新**:通过唯一标识符(identifier)更新现有评论,避免评论堆积。
|
||||||
|
|
||||||
|
## 输入参数
|
||||||
|
|
||||||
|
| 名称 | 描述 | 必填 | 默认值 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `gitea-token` | 用于 API 认证的 Gitea token | 是 | |
|
||||||
|
| `body` | 评论的 Markdown 内容 | 是 | |
|
||||||
|
| `identifier` | 评论的唯一标识符(用于更新) | 否 | |
|
||||||
|
| `update-if-exists` | 是否更新具有相同标识符的现有评论 | 否 | `false` |
|
||||||
|
| `event` | 评审类型 (`COMMENT`, `APPROVE`, `REQUEST_CHANGES`) | 否 | `COMMENT` |
|
||||||
|
| `commit-id` | 提交的 SHA(默认从 PR 自动获取) | 否 | |
|
||||||
|
| `path` | 文件相对路径(行级评论必填) | 否 | |
|
||||||
|
| `new-line` | 新文件中的行号 | 否 | |
|
||||||
|
| `old-line` | 旧文件中的行号 | 否 | |
|
||||||
|
|
||||||
|
### 高级/覆盖参数(通常自动推断)
|
||||||
|
|
||||||
|
| 名称 | 描述 | 默认值 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `gitea-base-url` | Gitea 实例的基础 URL | `GITEA_SERVER_URL` |
|
||||||
|
| `repository` | 仓库路径 (owner/repo) | `GITEA_REPOSITORY` |
|
||||||
|
| `issue-number` | PR 编号 | 自动从事件 Payload 获取 |
|
||||||
|
|
||||||
|
## 使用方法
|
||||||
|
|
||||||
|
### 1. 简单的评审总结
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: 批准 PR
|
||||||
|
uses: ren/gitea-comments@v0.1.0
|
||||||
|
with:
|
||||||
|
gitea-token: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
event: 'APPROVE'
|
||||||
|
body: "代码审阅通过! ✅"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 自动化的代码行评论(更新模式)
|
||||||
|
|
||||||
|
非常适合 Linter 或静态分析工具。
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: 提交代码规范建议
|
||||||
|
uses: ren/gitea-comments@v0.1.0
|
||||||
|
with:
|
||||||
|
gitea-token: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
path: "src/main.js"
|
||||||
|
new-line: 15
|
||||||
|
body: "建议在此处使用 `const` 而不是 `let`。"
|
||||||
|
identifier: lint-error-line-15
|
||||||
|
update-if-exists: true
|
||||||
|
```
|
||||||
|
|
||||||
|
## 许可证
|
||||||
|
|
||||||
|
MIT
|
||||||
47
action.yml
Normal file
47
action.yml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: 'Gitea PR Review'
|
||||||
|
description: 'Add or update code review comments on a Gitea Pull Request'
|
||||||
|
author: 'ren'
|
||||||
|
inputs:
|
||||||
|
gitea-token:
|
||||||
|
description: 'The Gitea token to authenticate with the API'
|
||||||
|
required: true
|
||||||
|
body:
|
||||||
|
description: 'The markdown body of the comment'
|
||||||
|
required: true
|
||||||
|
identifier:
|
||||||
|
description: 'A unique identifier for the comment, used for updating existing comments'
|
||||||
|
required: false
|
||||||
|
update-if-exists:
|
||||||
|
description: 'Whether to update an existing comment with the same identifier'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
event:
|
||||||
|
description: 'The review event (COMMENT, APPROVE, REQUEST_CHANGES)'
|
||||||
|
required: false
|
||||||
|
default: 'COMMENT'
|
||||||
|
commit-id:
|
||||||
|
description: 'The SHA of the commit (Automatically inferred from PR if not provided)'
|
||||||
|
required: false
|
||||||
|
path:
|
||||||
|
description: 'The relative path to the file (Required for line-specific comments)'
|
||||||
|
required: false
|
||||||
|
new-line:
|
||||||
|
description: 'The line number in the new file'
|
||||||
|
required: false
|
||||||
|
old-line:
|
||||||
|
description: 'The line number in the old file'
|
||||||
|
required: false
|
||||||
|
# Advanced/Override Inputs (Usually inferred from environment)
|
||||||
|
gitea-base-url:
|
||||||
|
description: 'The base URL of your Gitea instance (Automatically inferred if not provided)'
|
||||||
|
required: false
|
||||||
|
repository:
|
||||||
|
description: 'The repository (e.g., owner/repo) (Automatically inferred if not provided)'
|
||||||
|
required: false
|
||||||
|
issue-number:
|
||||||
|
description: 'The PR number to review (Automatically inferred if not provided)'
|
||||||
|
required: false
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: 'node20'
|
||||||
|
main: 'src/index.js'
|
||||||
18
package.json
Normal file
18
package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "gitea-comments",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "Gitea comments action",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"gitea",
|
||||||
|
"comments"
|
||||||
|
],
|
||||||
|
"author": "ren",
|
||||||
|
"private": true,
|
||||||
|
"packageManager": "pnpm@10.33.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/core": "^1.11.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
266
pnpm-lock.yaml
generated
Normal file
266
pnpm-lock.yaml
generated
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
'@actions/core':
|
||||||
|
specifier: ^1.11.1
|
||||||
|
version: 1.11.1
|
||||||
|
axios:
|
||||||
|
specifier: ^1.8.1
|
||||||
|
version: 1.15.0
|
||||||
|
devDependencies:
|
||||||
|
'@vercel/ncc':
|
||||||
|
specifier: ^0.38.3
|
||||||
|
version: 0.38.4
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@actions/core@1.11.1':
|
||||||
|
resolution: {integrity: sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==}
|
||||||
|
|
||||||
|
'@actions/exec@1.1.1':
|
||||||
|
resolution: {integrity: sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==}
|
||||||
|
|
||||||
|
'@actions/http-client@2.2.3':
|
||||||
|
resolution: {integrity: sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==}
|
||||||
|
|
||||||
|
'@actions/io@1.1.3':
|
||||||
|
resolution: {integrity: sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==}
|
||||||
|
|
||||||
|
'@fastify/busboy@2.1.1':
|
||||||
|
resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
'@vercel/ncc@0.38.4':
|
||||||
|
resolution: {integrity: sha512-8LwjnlP39s08C08J5NstzriPvW1SP8Zfpp1BvC2sI35kPeZnHfxVkCwu4/+Wodgnd60UtT1n8K8zw+Mp7J9JmQ==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
|
axios@1.15.0:
|
||||||
|
resolution: {integrity: sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-define-property@1.0.1:
|
||||||
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-errors@1.3.0:
|
||||||
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
follow-redirects@1.15.11:
|
||||||
|
resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
function-bind@1.1.2:
|
||||||
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
gopd@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-symbols@1.1.0:
|
||||||
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0:
|
||||||
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
proxy-from-env@2.1.0:
|
||||||
|
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
tunnel@0.0.6:
|
||||||
|
resolution: {integrity: sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==}
|
||||||
|
engines: {node: '>=0.6.11 <=0.7.0 || >=0.7.3'}
|
||||||
|
|
||||||
|
undici@5.29.0:
|
||||||
|
resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==}
|
||||||
|
engines: {node: '>=14.0'}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@actions/core@1.11.1':
|
||||||
|
dependencies:
|
||||||
|
'@actions/exec': 1.1.1
|
||||||
|
'@actions/http-client': 2.2.3
|
||||||
|
|
||||||
|
'@actions/exec@1.1.1':
|
||||||
|
dependencies:
|
||||||
|
'@actions/io': 1.1.3
|
||||||
|
|
||||||
|
'@actions/http-client@2.2.3':
|
||||||
|
dependencies:
|
||||||
|
tunnel: 0.0.6
|
||||||
|
undici: 5.29.0
|
||||||
|
|
||||||
|
'@actions/io@1.1.3': {}
|
||||||
|
|
||||||
|
'@fastify/busboy@2.1.1': {}
|
||||||
|
|
||||||
|
'@vercel/ncc@0.38.4': {}
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
|
axios@1.15.0:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.11
|
||||||
|
form-data: 4.0.5
|
||||||
|
proxy-from-env: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-errors: 1.3.0
|
||||||
|
gopd: 1.2.0
|
||||||
|
|
||||||
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.1:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
hasown: 2.0.2
|
||||||
|
|
||||||
|
follow-redirects@1.15.11: {}
|
||||||
|
|
||||||
|
form-data@4.0.5:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
function-bind: 1.1.2
|
||||||
|
get-proto: 1.0.1
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
hasown: 2.0.2
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
dunder-proto: 1.0.1
|
||||||
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
|
||||||
|
hasown@2.0.2:
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
|
proxy-from-env@2.1.0: {}
|
||||||
|
|
||||||
|
tunnel@0.0.6: {}
|
||||||
|
|
||||||
|
undici@5.29.0:
|
||||||
|
dependencies:
|
||||||
|
'@fastify/busboy': 2.1.1
|
||||||
76
src/gitea.js
Normal file
76
src/gitea.js
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Gitea API Client for Pull Request Reviews
|
||||||
|
*/
|
||||||
|
class GiteaClient {
|
||||||
|
constructor(baseUrl, token) {
|
||||||
|
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
||||||
|
this.token = token;
|
||||||
|
this.headers = {
|
||||||
|
'Authorization': `token ${this.token}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(path, options = {}) {
|
||||||
|
const url = `${this.baseUrl}/api/v1${path}`;
|
||||||
|
const fetchOptions = {
|
||||||
|
...options,
|
||||||
|
headers: {
|
||||||
|
...this.headers,
|
||||||
|
...options.headers
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, fetchOptions);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorBody = await response.text().catch(() => 'Unable to read error response');
|
||||||
|
throw new Error(`Gitea API Error: ${response.status} ${response.statusText} - ${errorBody}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.status === 204) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all review comments on a PR
|
||||||
|
*/
|
||||||
|
async listPullRequestComments(owner, repo, index) {
|
||||||
|
return await this.request(`/repos/${owner}/${repo}/pulls/${index}/comments`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a review comment on a specific line of a file in a PR
|
||||||
|
*/
|
||||||
|
async createPullRequestComment(owner, repo, index, { body, path, new_line, old_line, commit_id }) {
|
||||||
|
return await this.request(`/repos/${owner}/${repo}/pulls/${index}/comments`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ body, path, new_line, old_line, commit_id })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update an existing review comment
|
||||||
|
*/
|
||||||
|
async updatePullRequestComment(owner, repo, commentId, body) {
|
||||||
|
return await this.request(`/repos/${owner}/${repo}/pulls/comments/${commentId}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify({ body })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a review (summary + optional comments) on a PR
|
||||||
|
*/
|
||||||
|
async createReview(owner, repo, index, { body, event, comments = [] }) {
|
||||||
|
return await this.request(`/repos/${owner}/${repo}/pulls/${index}/reviews`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({ body, event, comments })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = GiteaClient;
|
||||||
104
src/index.js
Normal file
104
src/index.js
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
const core = require('@actions/core');
|
||||||
|
const fs = require('fs');
|
||||||
|
const GiteaClient = require('./gitea');
|
||||||
|
|
||||||
|
async function run() {
|
||||||
|
try {
|
||||||
|
const token = core.getInput('gitea-token', { required: true });
|
||||||
|
const body = core.getInput('body', { required: true });
|
||||||
|
const identifier = core.getInput('identifier');
|
||||||
|
const updateIfExists = core.getInput('update-if-exists') === 'true';
|
||||||
|
|
||||||
|
// Review Specific Inputs
|
||||||
|
const event = core.getInput('event') || 'COMMENT';
|
||||||
|
let commitId = core.getInput('commit-id');
|
||||||
|
const path = core.getInput('path');
|
||||||
|
const newLine = core.getInput('new-line');
|
||||||
|
const oldLine = core.getInput('old-line');
|
||||||
|
|
||||||
|
// Get parameters from environment variables or inputs
|
||||||
|
const baseUrl = core.getInput('gitea-base-url') || process.env.GITEA_SERVER_URL || process.env.GITHUB_SERVER_URL;
|
||||||
|
const repository = core.getInput('repository') || process.env.GITEA_REPOSITORY || process.env.GITHUB_REPOSITORY;
|
||||||
|
let index = core.getInput('issue-number');
|
||||||
|
|
||||||
|
if (!baseUrl) {
|
||||||
|
throw new Error('Gitea base URL not found. Please provide gitea-base-url input or set GITEA_SERVER_URL env.');
|
||||||
|
}
|
||||||
|
if (!repository) {
|
||||||
|
throw new Error('Repository not found. Please provide repository input or set GITEA_REPOSITORY env.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load event payload to get PR number and commit ID if not provided
|
||||||
|
const eventPath = process.env.GITEA_EVENT_PATH || process.env.GITHUB_EVENT_PATH;
|
||||||
|
if (eventPath && fs.existsSync(eventPath)) {
|
||||||
|
const eventPayload = JSON.parse(fs.readFileSync(eventPath, 'utf8'));
|
||||||
|
|
||||||
|
if (!index) {
|
||||||
|
index = eventPayload.pull_request ? eventPayload.pull_request.number : (eventPayload.issue ? eventPayload.issue.number : undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!commitId && eventPayload.pull_request) {
|
||||||
|
commitId = eventPayload.pull_request.head.sha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!index) {
|
||||||
|
throw new Error('Issue or PR number not found. Please provide issue-number input.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const [owner, repo] = repository.split('/');
|
||||||
|
const client = new GiteaClient(baseUrl, token);
|
||||||
|
|
||||||
|
const finalBody = identifier ? `${body}\n\n<!-- gitea-comment-identifier: ${identifier} -->` : body;
|
||||||
|
|
||||||
|
// 1. Line-specific Review Comment
|
||||||
|
if (path && commitId && (newLine || oldLine)) {
|
||||||
|
let existingComment = null;
|
||||||
|
|
||||||
|
if (identifier && updateIfExists) {
|
||||||
|
core.info(`Searching for existing line-specific comment with identifier: ${identifier}`);
|
||||||
|
try {
|
||||||
|
const comments = await client.listPullRequestComments(owner, repo, index);
|
||||||
|
const tag = `<!-- gitea-comment-identifier: ${identifier} -->`;
|
||||||
|
// Find comment that matches identifier AND path AND line
|
||||||
|
existingComment = comments.find(c =>
|
||||||
|
c.body.includes(tag) &&
|
||||||
|
c.path === path &&
|
||||||
|
(newLine ? c.new_line === parseInt(newLine) : c.old_line === parseInt(oldLine))
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
core.warning(`Error listing PR comments: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingComment) {
|
||||||
|
core.info(`Updating existing line-specific comment (ID: ${existingComment.id})`);
|
||||||
|
await client.updatePullRequestComment(owner, repo, existingComment.id, finalBody);
|
||||||
|
} else {
|
||||||
|
core.info(`Creating a new line-specific review comment on ${path}`);
|
||||||
|
await client.createPullRequestComment(owner, repo, index, {
|
||||||
|
body: finalBody,
|
||||||
|
path,
|
||||||
|
commit_id: commitId,
|
||||||
|
new_line: newLine ? parseInt(newLine) : undefined,
|
||||||
|
old_line: oldLine ? parseInt(oldLine) : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2. Summary Review
|
||||||
|
else {
|
||||||
|
core.info(`Creating a PR review with event: ${event}`);
|
||||||
|
await client.createReview(owner, repo, index, {
|
||||||
|
body: finalBody,
|
||||||
|
event: event
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info('Action completed successfully.');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
core.setFailed(`Action failed with error: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
Reference in New Issue
Block a user