Files
ulthon_admin/extend/base/common/command/admin/VersionBase.php
augushong 8e8c56ab53 chore: bump version to v2.2.0 and update full changelog
更新了版本号到v2.2.0,补充完整的版本更新说明,包含新功能、修复项、文档更新、重构、性能优化和其他变更内容
2026-05-26 20:34:57 +08:00

363 lines
13 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace base\common\command\admin;
use app\common\console\Command;
use app\common\tools\PathTools;
use think\App as ThinkApp;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
/**
* 版本管理与发布命令.
*
* 发布流程:
* 1. 修改 VERSION 常量为新版本号
* 2. php think admin:version --generate-comment
* → 从 git log 自动生成更新说明,输出到临时目录
* → 复制 commit_comment.php 中的数组内容到 COMMENT 常量
* 3. php think admin:version --push-tag
* → 创建 annotated tag 并推送到远程
* 4. [可选] php think admin:version --generate-release [--release-from-tag= vX.X.X]
* → 生成发布说明 Markdown手动发布到目标平台
*/
class VersionBase extends Command
{
public const VERSION = 'v2.2.0';
public const PRODUCT_VERSION = '';
public const LAYUI_VERSION = 'v2.10.1';
public const COMMENT = [
'版本更新说明:',
'【新功能】',
'- 新增日志每日自动清理定时任务',
'- 新增定时器配置、日志和主机的后台管理界面',
'- 新增 run_type 调度、host_id 投递和日志清理',
'- 新增配置同步到数据库及主节点选举',
'- 新增多节点定时器协调的 Scheme 定义',
'- 输出增强 -- 按目录分组、风险摘要、策略标注、--show 过滤',
'- 冲突策略参数替代交互确认',
'- 注册冲突策略和输出范围选项',
'- 后更新引导提示',
'- composer.json 智能对比输出具体命令',
'- 新增 --dry-run 预览模式',
'- 报错页面增加手机端适配,工具栏可折叠,修复原生布局溢出问题',
'- 报错页面增加AI友好格式和可配置一键复制功能',
'【修复】',
'- 主机列表页设为只读',
'- 防止主节点重复并修复 setMaster 权限',
'- 自动记录执行日志并捕获结果',
'- 修复 F2 代码质量问题',
'- 修复预览不实时更新、出错位置不生效、中文Unicode转义等问题预览改为右侧弹出面板增加字数统计',
'- 复选框变更时自动保存状态到localStorage',
'- 修复Markdown中文输出为Unicode转义的问题',
'- 修复云存储上传失败时静默返回空save_name的问题',
'【文档】',
'- 补充 admin:version 命令的发布流程注释与帮助说明',
'- 更新 SKILL.md 和 PROJECT.md 多节点协调文档',
'- 新增 ulthon-update-workflow AI 技能文档',
'- 注释掉docker-compose的name配置项',
'【重构】',
'- extend/think 归入可选更新范围',
'- 迁移根目录 docker/ 到 source/docker/,更新所有路径引用',
'【性能优化】',
'- clone 一次仓库并本地复制替代第二次网络 clone; 排除 public/storage 和 public/build',
'【其他】',
'- 更新 .gitignore 忽略文件',
'- 优化开发服务器静态资源加载router.php 注入缓存头',
'- 修改日志通道为debug_mysql',
];
protected const COMMIT_TYPES = [
'feat' => '新功能',
'fix' => '修复',
'docs' => '文档',
'style' => '样式',
'refactor' => '重构',
'perf' => '性能优化',
'test' => '测试',
'build' => '构建',
'ci' => '持续集成',
'chore' => '其他',
'revert' => '回退',
];
protected function configure()
{
parent::configure();
// 指令配置
$this->setName('admin:version')
->addOption('generate-comment', null, Option::VALUE_NONE, '使用git命令生成说明文件')
->addOption('generate-release', null, Option::VALUE_NONE, '使用git命令生成发布说明文件')
->addOption('release-from-tag', null, Option::VALUE_OPTIONAL, '从指定版本开始生成说明文件')
->addOption('push-tag', null, Option::VALUE_NONE, '使用git命令生成tag并推送')
->setDescription('查看当前版本号。可选 --generate-comment 生成更新说明、--push-tag 打tag推送、--generate-release 生成发布说明');
}
protected function execute(Input $input, Output $output)
{
// 文本模式输出
// 指令输出
if (!empty(static::PRODUCT_VERSION)) {
$output->info('当前版本号为:' . static::PRODUCT_VERSION);
}
$output->info('当前ulthon_admin版本号为' . static::VERSION);
$output->info('当前Layui版本号为' . static::LAYUI_VERSION);
$output->info('当前ThinkPHP版本号为' . app()->version());
$output->writeln('当前的修改说明:');
$output->writeln('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
foreach (static::COMMENT as $comment) {
$output->info($comment);
}
$output->writeln('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<');
$output->highlight('代码托管地址https://gitee.com/ulthon/ulthon_admin');
$output->highlight('开发文档地址http://doc.ulthon.com/home/read/ulthon_admin/home.html');
$is_push_tag = $input->hasOption('push-tag');
if ($is_push_tag) {
$this->pushTag();
}
$is_generate_comment = $input->hasOption('generate-comment');
if ($is_generate_comment) {
$this->generateComment();
}
$is_generate_release = $input->hasOption('generate-release');
if ($is_generate_release) {
$fromTag = $input->getOption('release-from-tag');
$this->generateRelease($fromTag);
}
}
protected function pushTag()
{
$input = $this->input;
$output = $this->output;
$output->writeln('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
$version = static::VERSION;
if (!empty(static::PRODUCT_VERSION)) {
$version = static::PRODUCT_VERSION;
}
// 将提交信息写入临时文件
$comment = implode("\n", static::COMMENT);
$output->info('生成标签:' . $version);
$output->info('标签描述:' . $comment);
// 使用项目的临时文件路径生成方法
$tempFile = PathTools::tempBuildPath('git_tag_message_' . uniqid() . '.txt');
file_put_contents($tempFile, $comment);
// 使用 -F 参数从文件读取提交信息
exec("git tag -a $version -F \"$tempFile\"");
// 删除临时文件
if (file_exists($tempFile)) {
unlink($tempFile);
}
$output->info('推送到远程仓库');
exec('git push --tags');
}
/**
* 生成版本更新说明
* 读取自上次tag到现在所有的提交说明.
* @return string
*/
protected function generateComment()
{
$this->output->writeln('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');
// 获取最新的tag
$lastTag = shell_exec('git describe --tags --abbrev=0');
$lastTag = trim($lastTag);
// 获取从最新tag到现在的所有提交
$commits = shell_exec("git log {$lastTag}..HEAD --pretty=format:\"%s\"");
if (empty($commits)) {
return '暂无更新说明';
}
$commits = explode("\n", $commits);
$groupedCommits = $this->groupCommitsByType($commits, function ($message) {
return trim($message);
});
// 生成更新说明
$comment = "版本更新说明:\n";
foreach (static::COMMIT_TYPES as $type => $desc) {
if (!empty($groupedCommits[$desc])) {
$comment .= "\n{$desc}\n";
foreach ($groupedCommits[$desc] as $message) {
$comment .= "- {$message}\n";
}
}
}
$this->output->writeln($comment);
// 生成数组格式文件
$comment_arr = explode("\n", $comment);
$comment_arr = array_filter($comment_arr, function ($item) {
return !empty($item);
});
$comment_arr = array_map(function ($item) {
return "'" . addslashes(trim($item)) . "',";
}, $comment_arr);
$comment_arr_code = implode("\n", $comment_arr);
$tmp_comment_file = PathTools::tempBuildPath('commit_comment.php');
file_put_contents($tmp_comment_file, "<?php\nreturn [\n" . $comment_arr_code . "\n];");
$this->output->info('已生成版本更新代码:' . $tmp_comment_file);
$tmp_comment_markdown_file = PathTools::tempBuildPath('commit_comment.md');
file_put_contents($tmp_comment_markdown_file, $comment);
$this->output->info('已生成版本更新Markdown' . $tmp_comment_markdown_file);
}
/**
* 生成发布说明文件
* 从指定版本开始生成发布说明.
* @param string|null $fromTag 起始版本号如果为空则使用最近的tag
* @return void
*/
public function generateRelease($fromTag = null)
{
$output = $this->output;
// 获取所有tags按时间倒序排列
$tags = explode("\n", trim(shell_exec('git tag --sort=-creatordate')));
if (empty($tags)) {
$output->error('没有找到任何tag');
return;
}
$toTag = $tags[0]; // 最新的tag
if (empty($fromTag)) {
if (count($tags) < 2) {
$output->error('没有足够的tag来生成发布说明');
return;
}
$fromTag = $tags[1]; // 倒数第二个tag
$output->info('未指定起始版本使用倒数第二个tag: ' . $fromTag);
} else {
if (!in_array($fromTag, $tags)) {
$output->error('指定的起始版本 ' . $fromTag . ' 不存在');
return;
}
if (version_compare($fromTag, $toTag, '>=')) {
$output->error('指定的起始版本 ' . $fromTag . ' 不早于最新版本 ' . $toTag);
return;
}
}
$output->info('从版本 ' . $fromTag . ' 生成发布说明');
$output->info('到最新tag: ' . $toTag);
// 获取当前版本
$currentVersion = static::PRODUCT_VERSION ?: static::VERSION;
// 获取从指定tag到目标tag的所有提交包含提交哈希和提交日期
$commits = shell_exec("git log {$fromTag}..{$toTag} --pretty=format:\"%h|%ad|%s\" --date=short");
if (empty($commits)) {
$output->warning('从 ' . $fromTag . ' 到 ' . $toTag . ' 没有任何提交');
return;
}
$commits = explode("\n", $commits);
$groupedCommits = $this->groupCommitsByType($commits, function ($message, $fullCommit) {
$parts = explode('|', $fullCommit);
if (count($parts) >= 3) {
return [
'message' => trim($message),
'hash' => $parts[0],
'date' => $parts[1],
];
}
return null;
});
// 生成发布说明
$releaseTitle = "## {$currentVersion} 发布说明\n\n";
$releaseDate = '发布日期: ' . date('Y-m-d') . "\n\n";
$releaseContent = "本次发布包含了从 {$fromTag}{$currentVersion} 的所有更新。\n\n";
foreach (static::COMMIT_TYPES as $type => $desc) {
if (!empty($groupedCommits[$desc])) {
$releaseContent .= "### {$desc}\n\n";
foreach ($groupedCommits[$desc] as $item) {
$releaseContent .= "- [{$item['date']}] {$item['message']} (#{$item['hash']})\n";
}
$releaseContent .= "\n";
}
}
$release = $releaseTitle . $releaseDate . $releaseContent;
// 生成发布说明文件
$releaseFile = PathTools::tempBuildPath("release_{$currentVersion}.md");
file_put_contents($releaseFile, $release);
$output->info('已生成发布说明文件: ' . $releaseFile);
}
protected function groupCommitsByType($commits, $formatCallback)
{
$groupedCommits = [];
foreach (static::COMMIT_TYPES as $type => $desc) {
$groupedCommits[$desc] = [];
}
$groupedCommits['其他'] = []; // 添加"其他"类别
foreach ($commits as $commit) {
$parts = explode('|', $commit);
$message = $parts[2] ?? $commit;
$matched = false;
foreach (static::COMMIT_TYPES as $type => $desc) {
// 支持 type: message 和 type(scope): message 两种格式
if (preg_match("/^{$type}(\([^)]*\))?\s*:/i", $message)) {
$cleanMessage = preg_replace("/^{$type}(\([^)]*\))?\s*:\s*/i", '', $message);
$groupedCommits[$desc][] = $formatCallback($cleanMessage, $commit);
$matched = true;
break;
}
}
// 如果没有匹配到任何类型,归类为"其他"
if (!$matched) {
$groupedCommits['其他'][] = $formatCallback($message, $commit);
}
}
// 清空空分组
foreach (array_keys($groupedCommits) as $key) {
if (empty($groupedCommits[$key])) {
unset($groupedCommits[$key]);
}
}
return $groupedCommits;
}
}