generated from actions/typescript-action
-
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
close #286
- Loading branch information
Showing
12 changed files
with
917 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,3 +14,4 @@ | |
SKIP_COMMENT = "/skip" | ||
|
||
REMOVE_LABEL = "Remove" | ||
CONFIG_LABEL = "Config" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
from githubkit.exception import RequestFailed | ||
from nonebot import logger, on_type | ||
from nonebot.adapters.github import GitHubBot | ||
from nonebot.adapters.github.event import ( | ||
IssueCommentCreated, | ||
IssuesEdited, | ||
IssuesOpened, | ||
IssuesReopened, | ||
PullRequestReviewSubmitted, | ||
) | ||
from nonebot.params import Depends | ||
|
||
from src.plugins.github.constants import CONFIG_LABEL, TITLE_MAX_LENGTH | ||
from src.plugins.github.depends import ( | ||
RepoInfo, | ||
bypass_git, | ||
get_installation_id, | ||
get_issue_handler, | ||
get_repo_info, | ||
get_type_by_labels_name, | ||
install_pre_commit_hooks, | ||
is_bot_triggered_workflow, | ||
) | ||
from src.plugins.github.models import IssueHandler | ||
from src.plugins.github.plugins.publish.render import render_comment | ||
from src.plugins.github.plugins.remove.depends import check_labels | ||
from src.plugins.github.typing import IssuesEvent | ||
from src.plugins.github.utils import run_shell_command | ||
from src.providers.validation.models import PublishType | ||
|
||
from .constants import BRANCH_NAME_PREFIX, COMMIT_MESSAGE_PREFIX, RESULTS_BRANCH | ||
from .utils import ( | ||
update_file, | ||
validate_info_from_issue, | ||
) | ||
|
||
|
||
async def check_rule( | ||
event: IssuesEvent, | ||
is_config: bool = check_labels(CONFIG_LABEL), | ||
is_bot: bool = Depends(is_bot_triggered_workflow), | ||
publish_type: PublishType = Depends(get_type_by_labels_name), | ||
) -> bool: | ||
if is_bot: | ||
logger.info("机器人触发的工作流,已跳过") | ||
return False | ||
if publish_type != PublishType.PLUGIN: | ||
logger.info("与插件无关,已跳过") | ||
return False | ||
if event.payload.issue.pull_request: | ||
logger.info("评论在拉取请求下,已跳过") | ||
return False | ||
if is_config is False: | ||
logger.info("非配置工作流,已跳过") | ||
return False | ||
return True | ||
|
||
|
||
config_check_matcher = on_type( | ||
(IssuesOpened, IssuesReopened, IssuesEdited, IssueCommentCreated), rule=check_rule | ||
) | ||
|
||
|
||
@config_check_matcher.handle( | ||
parameterless=[Depends(bypass_git), Depends(install_pre_commit_hooks)] | ||
) | ||
async def handle_remove_check( | ||
bot: GitHubBot, | ||
installation_id: int = Depends(get_installation_id), | ||
handler: IssueHandler = Depends(get_issue_handler), | ||
): | ||
async with bot.as_installation(installation_id): | ||
if handler.issue.state != "open": | ||
logger.info("议题未开启,已跳过") | ||
await config_check_matcher.finish() | ||
|
||
# 需要先切换到结果分支 | ||
run_shell_command(["git", "fetch", "origin", RESULTS_BRANCH]) | ||
run_shell_command(["git", "checkout", RESULTS_BRANCH]) | ||
|
||
# 检查是否满足发布要求 | ||
# 仅在通过检查的情况下创建拉取请求 | ||
result = await validate_info_from_issue(handler) | ||
|
||
# 渲染评论信息 | ||
comment = await render_comment(result, True) | ||
|
||
# 对议题评论 | ||
await handler.comment_issue(comment) | ||
|
||
branch_name = f"{BRANCH_NAME_PREFIX}{handler.issue_number}" | ||
|
||
# 设置拉取请求与议题的标题 | ||
# 限制标题长度,过长的标题不好看 | ||
title = f"{result.type}: {result.name[:TITLE_MAX_LENGTH]}" | ||
|
||
if result.valid: | ||
commit_message = f"{COMMIT_MESSAGE_PREFIX} {result.type.value.lower()} {result.name} (#{handler.issue_number})" | ||
|
||
# 创建新分支 | ||
run_shell_command(["git", "switch", "-C", branch_name]) | ||
# 更新文件 | ||
update_file(result) | ||
handler.commit_and_push(commit_message, branch_name, handler.author) | ||
# 创建拉取请求 | ||
try: | ||
await handler.create_pull_request( | ||
RESULTS_BRANCH, | ||
title, | ||
branch_name, | ||
[result.type.value, CONFIG_LABEL], | ||
) | ||
except RequestFailed: | ||
await handler.update_pull_request_status(title, branch_name) | ||
logger.info("该分支的拉取请求已创建,请前往查看") | ||
else: | ||
# 如果之前已经创建了拉取请求,则将其转换为草稿 | ||
await handler.draft_pull_request(branch_name) | ||
|
||
|
||
async def review_submitted_rule( | ||
event: PullRequestReviewSubmitted, | ||
is_config: bool = check_labels(CONFIG_LABEL), | ||
) -> bool: | ||
if not is_config: | ||
logger.info("拉取请求与配置无关,已跳过") | ||
return False | ||
if event.payload.review.author_association not in ["OWNER", "MEMBER"]: | ||
logger.info("审查者不是仓库成员,已跳过") | ||
return False | ||
if event.payload.review.state != "approved": | ||
logger.info("未通过审查,已跳过") | ||
return False | ||
|
||
return True | ||
|
||
|
||
auto_merge_matcher = on_type(PullRequestReviewSubmitted, rule=review_submitted_rule) | ||
|
||
|
||
@auto_merge_matcher.handle( | ||
parameterless=[Depends(bypass_git), Depends(install_pre_commit_hooks)] | ||
) | ||
async def handle_auto_merge( | ||
bot: GitHubBot, | ||
event: PullRequestReviewSubmitted, | ||
installation_id: int = Depends(get_installation_id), | ||
repo_info: RepoInfo = Depends(get_repo_info), | ||
) -> None: | ||
async with bot.as_installation(installation_id): | ||
# 如果有冲突的话,不会触发 Github Actions | ||
# 所以直接合并即可 | ||
await bot.rest.pulls.async_merge( | ||
**repo_info.model_dump(), | ||
pull_number=event.payload.pull_request.number, | ||
merge_method="rebase", | ||
) | ||
logger.info(f"已自动合并 #{event.payload.pull_request.number}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
RESULTS_BRANCH = "results" | ||
|
||
COMMIT_MESSAGE_PREFIX = "chore: edit config" | ||
BRANCH_NAME_PREFIX = "config/issue" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
from typing import Any | ||
|
||
from nonebot import logger | ||
|
||
from src.plugins.github.models import AuthorInfo | ||
from src.plugins.github.models.issue import IssueHandler | ||
from src.plugins.github.plugins.publish.constants import ( | ||
PLUGIN_CONFIG_PATTERN, | ||
PLUGIN_MODULE_NAME_PATTERN, | ||
PROJECT_LINK_PATTERN, | ||
) | ||
from src.plugins.github.plugins.publish.render import render_summary | ||
from src.plugins.github.plugins.publish.validation import add_step_summary, strip_ansi | ||
from src.plugins.github.utils import extract_issue_info_from_issue | ||
from src.providers.constants import PYPI_KEY_TEMPLATE | ||
from src.providers.docker_test import DockerPluginTest, Metadata | ||
from src.providers.models import RegistryPlugin, StoreTestResult | ||
from src.providers.utils import dump_json, load_json_from_file | ||
from src.providers.validation import PublishType, ValidationDict, validate_info | ||
from src.providers.validation.models import PluginPublishInfo | ||
|
||
|
||
async def validate_info_from_issue(handler: IssueHandler) -> ValidationDict: | ||
"""从议题中获取插件信息,并且运行插件测试加载且获取插件元信息后进行验证""" | ||
body = handler.issue.body if handler.issue.body else "" | ||
|
||
# 从议题里提取插件所需信息 | ||
raw_data: dict[str, Any] = extract_issue_info_from_issue( | ||
{ | ||
"module_name": PLUGIN_MODULE_NAME_PATTERN, | ||
"project_link": PROJECT_LINK_PATTERN, | ||
"test_config": PLUGIN_CONFIG_PATTERN, | ||
}, | ||
body, | ||
) | ||
# 从历史插件中获取标签 | ||
previous_plugins: dict[str, RegistryPlugin] = { | ||
PYPI_KEY_TEMPLATE.format( | ||
project_link=plugin["project_link"], module_name=plugin["module_name"] | ||
): RegistryPlugin(**plugin) | ||
for plugin in load_json_from_file("plugins.json") | ||
} | ||
raw_data["tags"] = previous_plugins[PYPI_KEY_TEMPLATE.format(**raw_data)].tags | ||
# 更新作者信息 | ||
raw_data.update(AuthorInfo.from_issue(handler.issue).model_dump()) | ||
|
||
module_name: str = raw_data.get("module_name", None) | ||
project_link: str = raw_data.get("project_link", None) | ||
test_config: str = raw_data.get("test_config", "") | ||
|
||
# 因为修改插件重新测试,所以上次的数据不需要加载,不然会报错重复 | ||
previous_data = [] | ||
|
||
# 修改插件配置肯定是为了通过插件测试,所以一定不跳过测试 | ||
raw_data["skip_test"] = False | ||
|
||
# 运行插件测试 | ||
test = DockerPluginTest(project_link, module_name, test_config) | ||
test_result = await test.run("3.12") | ||
|
||
# 去除颜色字符 | ||
test_output = strip_ansi("\n".join(test_result.outputs)) | ||
metadata = test_result.metadata | ||
if metadata: | ||
# 从插件测试结果中获得元数据 | ||
raw_data.update(metadata) | ||
|
||
# 更新插件测试结果 | ||
raw_data["version"] = test_result.version | ||
raw_data["load"] = test_result.load | ||
raw_data["test_output"] = test_output | ||
raw_data["metadata"] = bool(metadata) | ||
|
||
# 输出插件测试相关信息 | ||
add_step_summary(await render_summary(test_result, test_output, project_link)) | ||
logger.info( | ||
f"插件 {project_link}({test_result.version}) 插件加载{'成功' if test_result.load else '失败'} {'插件已尝试加载' if test_result.run else '插件并未开始运行'}" | ||
) | ||
logger.info(f"插件元数据:{metadata}") | ||
logger.info("插件测试输出:") | ||
for output in test_result.outputs: | ||
logger.info(output) | ||
|
||
# 验证插件相关信息 | ||
result = validate_info(PublishType.PLUGIN, raw_data, previous_data) | ||
|
||
if not result.valid_data.get("metadata"): | ||
# 如果没有跳过测试且缺少插件元数据,则跳过元数据相关的错误 | ||
# 因为这个时候这些项都会报错,错误在此时没有意义 | ||
metadata_keys = Metadata.__annotations__.keys() | ||
# 如果是重复报错,error["loc"] 是 () | ||
result.errors = [ | ||
error | ||
for error in result.errors | ||
if error["loc"] == () or error["loc"][0] not in metadata_keys | ||
] | ||
# 元数据缺失时,需要删除元数据相关的字段 | ||
for key in metadata_keys: | ||
result.valid_data.pop(key, None) | ||
|
||
return result | ||
|
||
|
||
def update_file(result: ValidationDict) -> None: | ||
"""更新文件""" | ||
if not isinstance(result.info, PluginPublishInfo): | ||
raise ValueError("仅支持修改插件配置") | ||
|
||
logger.info("正在更新配置文件和最新测试结果") | ||
|
||
# 读取文件 | ||
previous_plugins: dict[str, RegistryPlugin] = { | ||
PYPI_KEY_TEMPLATE.format( | ||
project_link=plugin["project_link"], module_name=plugin["module_name"] | ||
): RegistryPlugin(**plugin) | ||
for plugin in load_json_from_file("plugins.json") | ||
} | ||
previous_results: dict[str, StoreTestResult] = { | ||
key: StoreTestResult(**value) | ||
for key, value in load_json_from_file("results.json").items() | ||
} | ||
plugin_configs: dict[str, str] = load_json_from_file("plugin_configs.json") | ||
|
||
# 更新信息 | ||
plugin = RegistryPlugin.from_publish_info(result.info) | ||
previous_plugins[plugin.key] = plugin | ||
previous_results[plugin.key] = StoreTestResult.from_info(result.info) | ||
plugin_configs[plugin.key] = result.info.test_config | ||
|
||
dump_json("plugins.json", list(previous_plugins.values())) | ||
dump_json("results.json", previous_results) | ||
dump_json("plugin_configs.json", plugin_configs, False) | ||
|
||
logger.info("文件更新完成") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.