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