Files
ulthon_admin/extend/base/common/command/admin/VersionBase.php
2026-03-26 20:27:14 +08:00

376 lines
14 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;
class VersionBase extends Command
{
public const VERSION = 'v2.1.0';
public const PRODUCT_VERSION = '';
public const LAYUI_VERSION = 'v2.10.1';
public const COMMENT = [
'版本更新说明:',
'【新功能】',
'- 发布智能体版',
'- 新增source目录用于存放配套资源与多端代码',
'- 新增系统状态页面功能',
'- 完成基本的详情生成',
'- 初步完成详情生成',
'- 实现基本的详情生成',
'- 页面以子页面打卡时使用弹框方式增加和编辑',
'- 地图组件支持更多字段的回填',
'- 增加按级别查询日志',
'- 增加按应用、控制器、方法查询日志',
'- 增加访问方法',
'- 支持刷新',
'- 优化日志级别颜色',
'- 日志查看支持分组颜色标志',
'- 优化日志显示',
'- 完成基本的日志展示',
'【修复】',
'- 处理联表查询排序没有处理表名的问题',
'- 修复上传类生成必填显示错误问题',
'- 修复参数绑定不支持非get',
'- 修复添加和编辑页面选择模式下不能使用的问题',
'- 修复日志加载错误',
'- 保证complete能正确运行',
'- 修复系统节点表错误',
'- 修复创建系统节点表错误',
'【文档】',
'- 更新框架文档并新增技能说明',
'- 重构项目规则与技能文档结构并更新开发指南',
'- 更新 AGENTS.md 文档,补充登录认证机制说明',
'- 补充页面接口同体机制说明',
'- 添加命令行交互全局参数说明',
'- 更新项目文档中的框架版本信息',
'- 更新 AGENTS.md 中的文档链接和说明',
'- 添加 AGENTS.md 文件说明 AI 代理的开发约束与工具',
'- 修正Scheme相关命令的文档错误',
'- 在示例环境文件中添加MAIN数据库配置',
'- 更新CODERULE.md添加Scheme机制说明',
'【重构】',
'- 删除废弃的scheme类文件',
'【其他】',
'- refactor(uniapp): 重构工具函数和请求模块,提取通用逻辑',
'- feat(uniapp): 登录后立即获取用户资料并优化应用启动流程',
'- feat(uniapp): 集成 Pinia 状态管理并实现用户认证流程',
'- feat(tools/db): 在表结构描述中显示表注释',
'- fix(scheme): 改进MySQL表注释获取与对比的可靠性',
'- feat(console): 添加 --force-force 参数以跳过所有交互确认',
'- feat(command): 新增数据库调试命令行工具集',
'- docs(command): 添加tools命令命名规范文档',
'- style(welcome): 优化移动端响应式布局和样式细节',
'- refactor(admin): 重构系统状态模块以支持模块化结构',
'- fix(status): 修复系统状态页面布局问题并添加刷新功能',
'- feat(scheme): 增强 Scheme 与数据库同步机制并添加严格校验',
'- feat(upload): 为上传组件添加文件名显示和拖拽排序功能',
'- refactor(scheme): 优化数据库同步逻辑并增加备份表检查',
'- feat(同步命令): 添加忽略表功能并优化表名处理',
'- feat(scheme): 新增多个数据模型类文件',
'- fix(命令): 忽略备份表以避免在生成方案时包含它们',
'- refactor(DbToSchemeService): 重构字段注解生成逻辑以提高可读性',
'- feat(scheme): 新增数据库表结构同步方案',
'- docs(CODERULE): 重构并优化项目开发规则文档结构',
'- Merge branch \'feat-curd-read\'',
'- 删除测试代码',
'- 调整日志每页大小',
];
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('查看当前ulthon_admin的版本号');
}
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) {
if (preg_match("/^{$type}:/i", $message)) {
$cleanMessage = preg_replace("/^{$type}:/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;
}
}