Files
ulthon_admin/extend/base/common/command/admin/VersionBase.php
augushong 9a3442d7f5 feat: 更新版本至 v2.1.4 并同步更新日志
- 新增支持更新到 ulthon_admin 的 master 分支功能
- 修复目录忽略逻辑的前缀匹配误命中问题
- 更新 source 目录结构及相关文档说明
- 优化持续集成配置,避免覆盖现有 .env 文件
- 从 Git 跟踪中移除 .sisyphus 目录
2026-05-07 23:00:49 +08:00

325 lines
11 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.4';
public const PRODUCT_VERSION = '';
public const LAYUI_VERSION = 'v2.10.1';
public const COMMENT = [
'版本更新说明:',
'【新功能】',
'- 支持更新到 ulthon_admin 的 master 分支',
'【修复】',
'- 修复目录忽略逻辑的前缀匹配误命中问题',
'【文档】',
'- 更新 source 目录结构说明与相关文档',
'- 补充 source 子工程规则说明',
'- 更新方案定义技能文档的编写规范',
'【持续集成】',
'- 避免覆盖现有 .env 文件并设置 IS_DEMO 默认值',
'【其他】',
'- 从 Git 跟踪中移除 .sisyphus 目录',
];
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) {
// 支持 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;
}
}