'新功能', '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, "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; } }