Files
ulthon_admin/extend/base/common/command/admin/VersionBase.php
2025-05-07 15:19:20 +08:00

335 lines
12 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\tools\PathTools;
use think\App as ThinkApp;
use think\console\Command;
use think\console\Input;
use think\console\input\Option;
use think\console\Output;
class VersionBase extends Command
{
public const VERSION = 'v2.0.118';
public const PRODUCT_VERSION = '';
public const LAYUI_VERSION = 'v2.10.1';
public const COMMENT = [
'版本更新说明:',
'【新功能】',
'- 列表新增自动生成tab组件',
'- 视图增加$Controller对象可以在视图层访问控制器方法',
'- 优化上传组件绑定文件名输入框时不强制必须input支持textarea等',
'- 表格增加行选择的禁用状态设置回调函数,可以动态设置当前行是否可选',
'- 优化assign方法设置为隐藏数据后再次assign可以去除隐藏设置',
'- 优化验证器逻辑',
'- 将用户登录信息与普通缓存数据区分存储',
'- 增加动态设置当前账户的权限功能;增加该功能演示效果',
'- 增加ctype_numeric函数变相扩展验证器规则支持验证是否是合理数字。',
'- 新增地图不支持获取定位时使用ip定位',
'- 增加说明提示',
'- 表格按钮判断支持字符串1以方便直接在js中调用php的auth函数判断',
'- 统一修改code默认值0为正常500为默认异常其他数值异常',
'- 完善生成说明文件的代码结构和生成效果',
'【修复】',
'- 修复设置自定义form回调时未执行终止逻辑的问题导致的登录密码错误时再次请求失败',
'- 修复列表搜索重置导致所有预设搜索值失效的问题;',
'- 支持设置为searchfalse时初始化时传入的搜索参数持久的保留在表单中否则点击之后初始化的搜索项会丢失',
'- 修复通过回车提交表单时能够重复提交的问题',
'- 修复默认code值错误',
'- 修复登录页报错问题',
'- 修复生成发布说明包含了未打标签提交的问题',
'【重构】',
'- 重构用户信息状态代码写法。',
'- 优化代码结构',
];
protected const COMMIT_TYPES = [
'feat' => '新功能',
'fix' => '修复',
'docs' => '文档',
'style' => '样式',
'refactor' => '重构',
'perf' => '性能优化',
'test' => '测试',
'build' => '构建',
'ci' => '持续集成',
'chore' => '其他',
'revert' => '回退',
];
protected function 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版本号为' . ThinkApp::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;
}
}