docs: 更新命令文档和发布脚本
- 更新 release-android.md: 明确前置条件和通用项目支持 - 更新 auto-commit.md: 添加中英文 commit 信息平台选择规则 - 更新 commit-push.md: 添加中英文 commit 信息平台选择规则 - 更新 sync-oc-push.md: 添加中英文 commit 信息平台选择规则 - 更新 release-android.mjs: 支持从 tags 自动推断项目名,不再硬编码 android 前缀 - 更新 AGENTS.md: 补充中文交流规则的注意事项
This commit is contained in:
@@ -17,9 +17,14 @@
|
||||
* - GITEA_REPO (optional): Override repository name
|
||||
*
|
||||
* Usage:
|
||||
* node release-android.mjs [android-dir]
|
||||
* node release-android.mjs [--project <name>] [android-dir]
|
||||
*
|
||||
* If android-dir is not specified, will auto-detect from current directory.
|
||||
* --project <name> Specify project name (e.g., admin-tool, android)
|
||||
* android-dir Android project directory (optional, auto-detect if not specified)
|
||||
*
|
||||
* Examples:
|
||||
* node release-android.mjs --project admin-tool
|
||||
* node release-android.mjs --project android ./android
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
@@ -179,11 +184,76 @@ function readVersion(androidDir) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tag name based on project structure
|
||||
* Infer project name from Android directory name and git tags
|
||||
* Returns the prefix used in tags (e.g., "myapp", "android", or "")
|
||||
*/
|
||||
function getTagName(version, isMonorepoProject) {
|
||||
// Monorepo uses prefixed tag, standalone uses v-prefix
|
||||
return isMonorepoProject ? `android-${version}` : `v${version}`;
|
||||
function inferProjectName(gitRoot, androidDir) {
|
||||
try {
|
||||
// First, try to infer from Android directory name
|
||||
const dirName = path.basename(androidDir);
|
||||
|
||||
// Get all tags with version pattern
|
||||
const tags = execSync('git tag -l --sort=-version:refname', {
|
||||
cwd: gitRoot,
|
||||
encoding: 'utf-8',
|
||||
shell: true,
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim().split('\n').filter(Boolean);
|
||||
|
||||
if (tags.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If directory name matches any tag prefix, prefer that
|
||||
// For example: if dir is "admin-tool", look for "admin-tool-*" tags
|
||||
const dirPrefixTags = tags.filter(tag => tag.startsWith(`${dirName}-`));
|
||||
if (dirPrefixTags.length > 0) {
|
||||
return dirName;
|
||||
}
|
||||
|
||||
// If directory is "android" or "app", try "android" prefix
|
||||
if (dirName === 'android' || dirName === 'app') {
|
||||
const androidTags = tags.filter(tag => tag.startsWith('android-'));
|
||||
if (androidTags.length > 0) {
|
||||
return 'android';
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try to extract prefix from recent tags
|
||||
// Patterns: "myapp-1.0.0", "android-1.0.0", "v1.0.0"
|
||||
for (const tag of tags.slice(0, 10)) { // Check recent 10 tags
|
||||
const match = tag.match(/^(.+?)-\d+\.\d+/);
|
||||
if (match) {
|
||||
return match[1]; // Return prefix like "myapp", "android"
|
||||
}
|
||||
if (tag.match(/^v\d+\.\d+/)) {
|
||||
return ''; // v-prefix style (no project name)
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest tag that matches the pattern
|
||||
*/
|
||||
function getLatestTag(gitRoot, projectPrefix) {
|
||||
try {
|
||||
const pattern = projectPrefix ? `${projectPrefix}-*` : 'v*';
|
||||
const tag = execSync(`git tag -l '${pattern}' --sort=-version:refname | head -1`, {
|
||||
cwd: gitRoot,
|
||||
encoding: 'utf-8',
|
||||
shell: true,
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
|
||||
return tag || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +306,7 @@ function getTagInfo(tagName, gitRoot) {
|
||||
* Build Android APK
|
||||
*/
|
||||
function buildApk(androidDir, javaHome) {
|
||||
console.log('Building APK...');
|
||||
console.log('正在构建 APK...');
|
||||
execSync('./gradlew assembleRelease --quiet', {
|
||||
cwd: androidDir,
|
||||
stdio: 'inherit',
|
||||
@@ -290,12 +360,12 @@ function uploadToGitea(config) {
|
||||
const existingRelease = releases.find((r) => r.tag_name === tagName);
|
||||
if (existingRelease) {
|
||||
releaseId = existingRelease.id;
|
||||
console.log(`Found existing Release (ID: ${releaseId})`);
|
||||
console.log(`找到已存在的 Release (ID: ${releaseId})`);
|
||||
|
||||
// Delete existing asset with same name
|
||||
const existingAsset = (existingRelease.assets || []).find((a) => a.name === fileName);
|
||||
if (existingAsset) {
|
||||
console.log(`Deleting existing asset: ${fileName}`);
|
||||
console.log(`删除已存在的文件: ${fileName}`);
|
||||
execSync(
|
||||
`curl -s -X DELETE -H "Authorization: token ${token}" "${repoApiBase}/releases/${releaseId}/assets/${existingAsset.id}"`,
|
||||
{ shell: true, stdio: ['pipe', 'pipe', 'pipe'] }
|
||||
@@ -305,7 +375,7 @@ function uploadToGitea(config) {
|
||||
}
|
||||
|
||||
if (!releaseId) {
|
||||
console.log('Creating new Release...');
|
||||
console.log('创建新 Release...');
|
||||
const releaseData = {
|
||||
tag_name: tagName,
|
||||
name: `Android APK ${tagName}`,
|
||||
@@ -331,11 +401,11 @@ function uploadToGitea(config) {
|
||||
}
|
||||
|
||||
releaseId = releaseInfo.id;
|
||||
console.log(`Release created (ID: ${releaseId})`);
|
||||
console.log(`Release 已创建 (ID: ${releaseId})`);
|
||||
}
|
||||
|
||||
// Upload APK
|
||||
console.log('Uploading APK...');
|
||||
console.log('上传 APK...');
|
||||
const uploadUrl = `${repoApiBase}/releases/${releaseId}/assets?name=${encodeURIComponent(fileName)}`;
|
||||
const uploadResult = curlJson(
|
||||
`curl -s -X POST "${uploadUrl}" \
|
||||
@@ -357,12 +427,33 @@ function uploadToGitea(config) {
|
||||
// Main
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Parse command line arguments
|
||||
*/
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
let projectName = null;
|
||||
let androidDir = null;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === '--project' && i + 1 < args.length) {
|
||||
projectName = args[i + 1];
|
||||
i++; // skip next arg
|
||||
} else if (!arg.startsWith('--')) {
|
||||
androidDir = arg;
|
||||
}
|
||||
}
|
||||
|
||||
return { projectName, androidDir };
|
||||
}
|
||||
|
||||
function main() {
|
||||
const cwd = process.cwd();
|
||||
const argDir = process.argv[2];
|
||||
const { projectName, androidDir: argDir } = parseArgs();
|
||||
|
||||
console.log('='.repeat(60));
|
||||
console.log('Android Release Script');
|
||||
console.log('Android 发布脚本');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 1. Find Android project root
|
||||
@@ -370,23 +461,23 @@ function main() {
|
||||
const androidDir = findAndroidRoot(searchDir);
|
||||
|
||||
if (!androidDir) {
|
||||
console.error('Error: Cannot find Android project');
|
||||
console.error('Make sure you are in an Android project directory or specify the path');
|
||||
console.error('Usage: release-android [android-dir]');
|
||||
console.error('错误:找不到 Android 项目');
|
||||
console.error('请确保当前目录是 Android 项目或指定正确的路径');
|
||||
console.error('用法: release-android [android-dir]');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Android project: ${androidDir}`);
|
||||
console.log(`Android 项目: ${androidDir}`);
|
||||
|
||||
// 2. Find git root
|
||||
const gitRoot = findGitRoot(androidDir);
|
||||
if (!gitRoot) {
|
||||
console.error('Error: Not a git repository');
|
||||
console.error('错误:不是 git 仓库');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Git root: ${gitRoot}`);
|
||||
console.log(`Git 根目录: ${gitRoot}`);
|
||||
|
||||
const monorepo = isMonorepo(androidDir, gitRoot);
|
||||
console.log(`Project type: ${monorepo ? 'Monorepo' : 'Standalone'}`);
|
||||
console.log(`项目类型: ${monorepo ? 'Monorepo' : '独立项目'}`);
|
||||
|
||||
// 3. Detect Gitea configuration
|
||||
const detected = detectGiteaConfig(gitRoot);
|
||||
@@ -396,14 +487,14 @@ function main() {
|
||||
const GITEA_REPO = process.env.GITEA_REPO || detected.repo || '';
|
||||
|
||||
if (!GITEA_TOKEN) {
|
||||
console.error('Error: GITEA_TOKEN environment variable is not set');
|
||||
console.error('Please set: export GITEA_TOKEN="your_token"');
|
||||
console.error('错误:未设置 GITEA_TOKEN 环境变量');
|
||||
console.error('请设置: export GITEA_TOKEN="your_token"');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!GITEA_API_URL || !GITEA_OWNER || !GITEA_REPO) {
|
||||
console.error('Error: Cannot detect Gitea repository configuration');
|
||||
console.error('Please set environment variables: GITEA_API_URL, GITEA_OWNER, GITEA_REPO');
|
||||
console.error('错误:无法检测 Gitea 仓库配置');
|
||||
console.error('请设置环境变量: GITEA_API_URL, GITEA_OWNER, GITEA_REPO');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -412,20 +503,73 @@ function main() {
|
||||
// 4. Read version
|
||||
const version = readVersion(androidDir);
|
||||
if (!version) {
|
||||
console.error('Error: Cannot read versionName from build.gradle');
|
||||
console.error('错误:无法从 build.gradle 读取 versionName');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Version: ${version}`);
|
||||
console.log(`版本: ${version}`);
|
||||
|
||||
// 5. Check tag
|
||||
const tagName = getTagName(version, monorepo);
|
||||
const tagInfo = getTagInfo(tagName, gitRoot);
|
||||
// 5. Determine project prefix
|
||||
let projectPrefix;
|
||||
|
||||
if (projectName) {
|
||||
// Use provided project name
|
||||
console.log(`项目前缀: ${projectName} (指定)`);
|
||||
projectPrefix = projectName;
|
||||
|
||||
// Verify that tags with this prefix exist
|
||||
const testTag = getLatestTag(gitRoot, projectPrefix);
|
||||
if (!testTag) {
|
||||
console.error(`错误:找不到匹配 "${projectPrefix}-*" 模式的 git tag`);
|
||||
console.error('');
|
||||
console.error('可用的 tags:');
|
||||
try {
|
||||
const allTags = execSync('git tag -l --sort=-version:refname | head -10', {
|
||||
cwd: gitRoot,
|
||||
encoding: 'utf-8',
|
||||
shell: true,
|
||||
stdio: ['pipe', 'pipe', 'pipe']
|
||||
}).trim();
|
||||
console.error(allTags || ' (未找到 tags)');
|
||||
} catch {
|
||||
console.error(' (无法列出 tags)');
|
||||
}
|
||||
console.error('');
|
||||
console.error(`请创建正确前缀的 tag:`);
|
||||
console.error(` git tag -a ${projectPrefix}-${version} -m "发布说明"`);
|
||||
console.error(` git push origin ${projectPrefix}-${version}`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
// Infer project name from directory and tags
|
||||
projectPrefix = inferProjectName(gitRoot, androidDir);
|
||||
if (projectPrefix === null) {
|
||||
console.error('错误:无法从 git tags 推断项目名称');
|
||||
console.error('请确保至少存在一个以下格式的 tag:');
|
||||
console.error(' myapp-1.0.0 或 v1.0.0');
|
||||
console.error('');
|
||||
console.error('或使用 --project 明确指定项目名称:');
|
||||
console.error(' node release-android.mjs --project admin-tool');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`项目前缀: ${projectPrefix || '(v-style)'} (自动检测)`);
|
||||
}
|
||||
|
||||
if (!tagInfo.exists) {
|
||||
console.error(`Error: Git tag "${tagName}" not found`);
|
||||
const tagName = getLatestTag(gitRoot, projectPrefix);
|
||||
if (!tagName) {
|
||||
const expectedTag = projectPrefix ? `${projectPrefix}-${version}` : `v${version}`;
|
||||
console.error(`错误:找不到匹配的 git tag`);
|
||||
console.error(`期望的 tag 格式: ${expectedTag}`);
|
||||
console.error('');
|
||||
console.error('Please create a tag before releasing:');
|
||||
console.error(` git tag -a ${tagName} -m "Release notes"`);
|
||||
console.error('请先创建 tag:');
|
||||
console.error(` git tag -a ${expectedTag} -m "发布说明"`);
|
||||
console.error(` git push origin ${expectedTag}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tagInfo = getTagInfo(tagName, gitRoot);
|
||||
if (!tagInfo.exists) {
|
||||
console.error(`错误:Git tag "${tagName}" 不存在`);
|
||||
console.error('请推送 tag:');
|
||||
console.error(` git push origin ${tagName}`);
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -434,37 +578,39 @@ function main() {
|
||||
// 6. Detect Java
|
||||
const javaHome = detectJavaHome();
|
||||
if (!javaHome) {
|
||||
console.error('Error: Cannot find Java Runtime');
|
||||
console.error('Please install JDK or ensure Android Studio is properly installed');
|
||||
console.error('错误:找不到 Java 运行环境');
|
||||
console.error('请安装 JDK 或确保 Android Studio 已正确安装');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`Java: ${javaHome}`);
|
||||
|
||||
console.log('');
|
||||
console.log('Building...');
|
||||
console.log('开始构建...');
|
||||
|
||||
// 7. Build APK
|
||||
try {
|
||||
buildApk(androidDir, javaHome);
|
||||
} catch (err) {
|
||||
console.error('Build failed:', err.message);
|
||||
console.error('构建失败:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 8. Find APK
|
||||
const apk = findApk(androidDir);
|
||||
if (!apk) {
|
||||
console.error('Error: APK file not found');
|
||||
console.error('Check build output at: app/build/outputs/apk/release/');
|
||||
console.error('错误:找不到 APK 文件');
|
||||
console.error('检查构建输出: app/build/outputs/apk/release/');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`APK: ${apk.path} (${apk.signed ? 'signed' : 'unsigned'})`);
|
||||
console.log(`APK: ${apk.path} (${apk.signed ? '已签名' : '未签名'})`);
|
||||
|
||||
// 9. Upload to Gitea
|
||||
const fileName = `${GITEA_REPO}-android-${version}.apk`;
|
||||
const fileName = projectPrefix
|
||||
? `${projectPrefix}-${version}.apk`
|
||||
: `${GITEA_REPO}-${version}.apk`;
|
||||
|
||||
console.log('');
|
||||
console.log('Uploading to Gitea...');
|
||||
console.log('上传到 Gitea...');
|
||||
|
||||
try {
|
||||
const result = uploadToGitea({
|
||||
@@ -480,13 +626,13 @@ function main() {
|
||||
|
||||
console.log('');
|
||||
console.log('='.repeat(60));
|
||||
console.log('Release successful!');
|
||||
console.log(`File: ${fileName}`);
|
||||
console.log(`Size: ${(result.fileSize / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log('发布成功!');
|
||||
console.log(`文件: ${fileName}`);
|
||||
console.log(`大小: ${(result.fileSize / 1024 / 1024).toFixed(2)} MB`);
|
||||
console.log(`URL: ${result.releaseUrl}`);
|
||||
console.log('='.repeat(60));
|
||||
} catch (err) {
|
||||
console.error('Upload failed:', err.message);
|
||||
console.error('上传失败:', err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user