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:
2026-01-09 16:54:56 +08:00
parent 0e161b3088
commit dce31f9729
6 changed files with 292 additions and 115 deletions

View File

@@ -9,4 +9,21 @@
- 用户明确要求使用其他语言 - 用户明确要求使用其他语言
- 技术术语在中文中没有合适的翻译(可以中英文混用,如 "React Hooks" - 技术术语在中文中没有合适的翻译(可以中英文混用,如 "React Hooks"
这是用户的个人偏好设置,适用于所有项目。 ### 重要注意事项
1. **不要因为文档、命令或代码是英文就切换语言**
- 即使项目文档是英文的,回复时仍然用中文解释
- 即使执行的命令是英文的,回复时仍然用中文说明
- 即使错误信息是英文的,回复时仍然用中文分析
2. **工具输出可以保持原语言**
- 代码本身使用英文(函数名、变量名等)
- 终端命令使用英文
- 但你对这些内容的解释和说明必须用中文
3. **用户交互始终用中文**
- 所有对话、提问、确认都用中文
- 所有建议、总结、分析都用中文
- 所有错误解释和问题诊断都用中文
这是用户的个人偏好设置,适用于所有项目和所有交互场景。

View File

@@ -17,9 +17,14 @@
* - GITEA_REPO (optional): Override repository name * - GITEA_REPO (optional): Override repository name
* *
* Usage: * 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'; 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) { function inferProjectName(gitRoot, androidDir) {
// Monorepo uses prefixed tag, standalone uses v-prefix try {
return isMonorepoProject ? `android-${version}` : `v${version}`; // 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 * Build Android APK
*/ */
function buildApk(androidDir, javaHome) { function buildApk(androidDir, javaHome) {
console.log('Building APK...'); console.log('正在构建 APK...');
execSync('./gradlew assembleRelease --quiet', { execSync('./gradlew assembleRelease --quiet', {
cwd: androidDir, cwd: androidDir,
stdio: 'inherit', stdio: 'inherit',
@@ -290,12 +360,12 @@ function uploadToGitea(config) {
const existingRelease = releases.find((r) => r.tag_name === tagName); const existingRelease = releases.find((r) => r.tag_name === tagName);
if (existingRelease) { if (existingRelease) {
releaseId = existingRelease.id; releaseId = existingRelease.id;
console.log(`Found existing Release (ID: ${releaseId})`); console.log(`找到已存在的 Release (ID: ${releaseId})`);
// Delete existing asset with same name // Delete existing asset with same name
const existingAsset = (existingRelease.assets || []).find((a) => a.name === fileName); const existingAsset = (existingRelease.assets || []).find((a) => a.name === fileName);
if (existingAsset) { if (existingAsset) {
console.log(`Deleting existing asset: ${fileName}`); console.log(`删除已存在的文件: ${fileName}`);
execSync( execSync(
`curl -s -X DELETE -H "Authorization: token ${token}" "${repoApiBase}/releases/${releaseId}/assets/${existingAsset.id}"`, `curl -s -X DELETE -H "Authorization: token ${token}" "${repoApiBase}/releases/${releaseId}/assets/${existingAsset.id}"`,
{ shell: true, stdio: ['pipe', 'pipe', 'pipe'] } { shell: true, stdio: ['pipe', 'pipe', 'pipe'] }
@@ -305,7 +375,7 @@ function uploadToGitea(config) {
} }
if (!releaseId) { if (!releaseId) {
console.log('Creating new Release...'); console.log('创建新 Release...');
const releaseData = { const releaseData = {
tag_name: tagName, tag_name: tagName,
name: `Android APK ${tagName}`, name: `Android APK ${tagName}`,
@@ -331,11 +401,11 @@ function uploadToGitea(config) {
} }
releaseId = releaseInfo.id; releaseId = releaseInfo.id;
console.log(`Release created (ID: ${releaseId})`); console.log(`Release 已创建 (ID: ${releaseId})`);
} }
// Upload APK // Upload APK
console.log('Uploading APK...'); console.log('上传 APK...');
const uploadUrl = `${repoApiBase}/releases/${releaseId}/assets?name=${encodeURIComponent(fileName)}`; const uploadUrl = `${repoApiBase}/releases/${releaseId}/assets?name=${encodeURIComponent(fileName)}`;
const uploadResult = curlJson( const uploadResult = curlJson(
`curl -s -X POST "${uploadUrl}" \ `curl -s -X POST "${uploadUrl}" \
@@ -357,12 +427,33 @@ function uploadToGitea(config) {
// Main // 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() { function main() {
const cwd = process.cwd(); const cwd = process.cwd();
const argDir = process.argv[2]; const { projectName, androidDir: argDir } = parseArgs();
console.log('='.repeat(60)); console.log('='.repeat(60));
console.log('Android Release Script'); console.log('Android 发布脚本');
console.log('='.repeat(60)); console.log('='.repeat(60));
// 1. Find Android project root // 1. Find Android project root
@@ -370,23 +461,23 @@ function main() {
const androidDir = findAndroidRoot(searchDir); const androidDir = findAndroidRoot(searchDir);
if (!androidDir) { if (!androidDir) {
console.error('Error: Cannot find Android project'); console.error('错误:找不到 Android 项目');
console.error('Make sure you are in an Android project directory or specify the path'); console.error('请确保当前目录是 Android 项目或指定正确的路径');
console.error('Usage: release-android [android-dir]'); console.error('用法: release-android [android-dir]');
process.exit(1); process.exit(1);
} }
console.log(`Android project: ${androidDir}`); console.log(`Android 项目: ${androidDir}`);
// 2. Find git root // 2. Find git root
const gitRoot = findGitRoot(androidDir); const gitRoot = findGitRoot(androidDir);
if (!gitRoot) { if (!gitRoot) {
console.error('Error: Not a git repository'); console.error('错误:不是 git 仓库');
process.exit(1); process.exit(1);
} }
console.log(`Git root: ${gitRoot}`); console.log(`Git 根目录: ${gitRoot}`);
const monorepo = isMonorepo(androidDir, gitRoot); const monorepo = isMonorepo(androidDir, gitRoot);
console.log(`Project type: ${monorepo ? 'Monorepo' : 'Standalone'}`); console.log(`项目类型: ${monorepo ? 'Monorepo' : '独立项目'}`);
// 3. Detect Gitea configuration // 3. Detect Gitea configuration
const detected = detectGiteaConfig(gitRoot); const detected = detectGiteaConfig(gitRoot);
@@ -396,14 +487,14 @@ function main() {
const GITEA_REPO = process.env.GITEA_REPO || detected.repo || ''; const GITEA_REPO = process.env.GITEA_REPO || detected.repo || '';
if (!GITEA_TOKEN) { if (!GITEA_TOKEN) {
console.error('Error: GITEA_TOKEN environment variable is not set'); console.error('错误:未设置 GITEA_TOKEN 环境变量');
console.error('Please set: export GITEA_TOKEN="your_token"'); console.error('请设置: export GITEA_TOKEN="your_token"');
process.exit(1); process.exit(1);
} }
if (!GITEA_API_URL || !GITEA_OWNER || !GITEA_REPO) { if (!GITEA_API_URL || !GITEA_OWNER || !GITEA_REPO) {
console.error('Error: Cannot detect Gitea repository configuration'); console.error('错误:无法检测 Gitea 仓库配置');
console.error('Please set environment variables: GITEA_API_URL, GITEA_OWNER, GITEA_REPO'); console.error('请设置环境变量: GITEA_API_URL, GITEA_OWNER, GITEA_REPO');
process.exit(1); process.exit(1);
} }
@@ -412,20 +503,73 @@ function main() {
// 4. Read version // 4. Read version
const version = readVersion(androidDir); const version = readVersion(androidDir);
if (!version) { if (!version) {
console.error('Error: Cannot read versionName from build.gradle'); console.error('错误:无法从 build.gradle 读取 versionName');
process.exit(1); process.exit(1);
} }
console.log(`Version: ${version}`); console.log(`版本: ${version}`);
// 5. Check tag // 5. Determine project prefix
const tagName = getTagName(version, monorepo); let projectPrefix;
const tagInfo = getTagInfo(tagName, gitRoot);
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) { const tagName = getLatestTag(gitRoot, projectPrefix);
console.error(`Error: Git tag "${tagName}" not found`); if (!tagName) {
const expectedTag = projectPrefix ? `${projectPrefix}-${version}` : `v${version}`;
console.error(`错误:找不到匹配的 git tag`);
console.error(`期望的 tag 格式: ${expectedTag}`);
console.error(''); console.error('');
console.error('Please create a tag before releasing:'); console.error('请先创建 tag:');
console.error(` git tag -a ${tagName} -m "Release notes"`); 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}`); console.error(` git push origin ${tagName}`);
process.exit(1); process.exit(1);
} }
@@ -434,37 +578,39 @@ function main() {
// 6. Detect Java // 6. Detect Java
const javaHome = detectJavaHome(); const javaHome = detectJavaHome();
if (!javaHome) { if (!javaHome) {
console.error('Error: Cannot find Java Runtime'); console.error('错误:找不到 Java 运行环境');
console.error('Please install JDK or ensure Android Studio is properly installed'); console.error('请安装 JDK 或确保 Android Studio 已正确安装');
process.exit(1); process.exit(1);
} }
console.log(`Java: ${javaHome}`); console.log(`Java: ${javaHome}`);
console.log(''); console.log('');
console.log('Building...'); console.log('开始构建...');
// 7. Build APK // 7. Build APK
try { try {
buildApk(androidDir, javaHome); buildApk(androidDir, javaHome);
} catch (err) { } catch (err) {
console.error('Build failed:', err.message); console.error('构建失败:', err.message);
process.exit(1); process.exit(1);
} }
// 8. Find APK // 8. Find APK
const apk = findApk(androidDir); const apk = findApk(androidDir);
if (!apk) { if (!apk) {
console.error('Error: APK file not found'); console.error('错误:找不到 APK 文件');
console.error('Check build output at: app/build/outputs/apk/release/'); console.error('检查构建输出: app/build/outputs/apk/release/');
process.exit(1); process.exit(1);
} }
console.log(`APK: ${apk.path} (${apk.signed ? 'signed' : 'unsigned'})`); console.log(`APK: ${apk.path} (${apk.signed ? '已签名' : '未签名'})`);
// 9. Upload to Gitea // 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('');
console.log('Uploading to Gitea...'); console.log('上传到 Gitea...');
try { try {
const result = uploadToGitea({ const result = uploadToGitea({
@@ -480,13 +626,13 @@ function main() {
console.log(''); console.log('');
console.log('='.repeat(60)); console.log('='.repeat(60));
console.log('Release successful!'); console.log('发布成功!');
console.log(`File: ${fileName}`); console.log(`文件: ${fileName}`);
console.log(`Size: ${(result.fileSize / 1024 / 1024).toFixed(2)} MB`); console.log(`大小: ${(result.fileSize / 1024 / 1024).toFixed(2)} MB`);
console.log(`URL: ${result.releaseUrl}`); console.log(`URL: ${result.releaseUrl}`);
console.log('='.repeat(60)); console.log('='.repeat(60));
} catch (err) { } catch (err) {
console.error('Upload failed:', err.message); console.error('上传失败:', err.message);
process.exit(1); process.exit(1);
} }
} }

View File

@@ -130,9 +130,13 @@ Based on changes in staging area, **analyze and generate meaningful commit messa
- Use [Conventional Commits](https://www.conventionalcommits.org/) specification - Use [Conventional Commits](https://www.conventionalcommits.org/) specification
- Commit message should concisely but accurately describe "why" rather than just "what" - Commit message should concisely but accurately describe "why" rather than just "what"
- Common types: `feat` (new feature), `fix` (fix), `docs` (documentation), `refactor` (refactoring), `chore` (miscellaneous), `test` (test), etc. - Common types: `feat` (new feature), `fix` (fix), `docs` (documentation), `refactor` (refactoring), `chore` (miscellaneous), `test` (test), etc.
- **Language selection**:
- **Default (macOS/Linux)**: Use Chinese (中文) for commit messages
- **Windows**: Use English due to encoding issues with Cursor Shell tool
- **If monorepo and this commit only affects single subproject, include subproject name in commit message**: - **If monorepo and this commit only affects single subproject, include subproject name in commit message**:
- Format: `<type>(<scope>): <description>`, where `<scope>` is subproject name - Format: `<type>(<scope>): <description>`, where `<scope>` is subproject name
- Example: `feat(ios): support OGG Opus upload` or `fix(electron): fix clipboard injection failure` - Example (Chinese): `feat(ios): 支持 OGG Opus 上传` or `fix(electron): 修复剪贴板注入失败`
- Example (English): `feat(ios): support OGG Opus upload` or `fix(electron): fix clipboard injection failure`
- If affecting multiple subprojects or entire repository, no need to add scope - If affecting multiple subprojects or entire repository, no need to add scope
### 6. Update Project Version Number ### 6. Update Project Version Number
@@ -149,11 +153,27 @@ Based on changes in staging area, **analyze and generate meaningful commit messa
Execute commit with generated commit message (staging area now includes user's changes and version number update). Execute commit with generated commit message (staging area now includes user's changes and version number update).
**Windows encoding issue solution:** **Commit message language by platform:**
Cursor's Shell tool has encoding issues when passing Chinese parameters on Windows, causing garbled Git commit messages. **macOS/Linux** (use Chinese commit messages):
**Correct approach**: Use **English** commit message ```bash
# Single line commit
git commit -m "feat(android): 添加新功能"
# Multi-line commit (recommended)
git commit -m "$(cat <<'EOF'
feat(android): 添加新功能
- 详细说明 1
- 详细说明 2
EOF
)"
```
**Windows** (use English commit messages):
Due to Cursor's Shell tool encoding issues on Windows, **must use English** commit messages.
```powershell ```powershell
# Single line commit # Single line commit
@@ -163,26 +183,12 @@ git commit -m "feat(android): add new feature"
git commit --message="fix(android): fix code review issues" --message="- Fix syntax error" --message="- Use JSONObject instead of manual JSON" git commit --message="fix(android): fix code review issues" --message="- Fix syntax error" --message="- Use JSONObject instead of manual JSON"
``` ```
**Prohibited methods** (will cause garbled text): **Windows prohibited methods** (will cause garbled text):
- No Chinese commit messages - Cursor Shell tool encoding error when passing Chinese parameters - No Chinese commit messages - Cursor Shell tool encoding error when passing Chinese parameters
- No `Out-File -Encoding utf8` - writes UTF-8 with BOM - No `Out-File -Encoding utf8` - writes UTF-8 with BOM
- No Write tool to write temp files - encoding uncontrollable - No Write tool to write temp files - encoding uncontrollable
- No PowerShell here-string `@"..."@` - newline parsing issues - No PowerShell here-string `@"..."@` - newline parsing issues
**For Chinese commit messages**: Please manually execute `git commit` in terminal, or use Git GUI tools other than Cursor.
**macOS/Linux alternative** (supports Chinese):
```bash
git commit -m "$(cat <<'EOF'
feat(android): new feature description
- Detail 1
- Detail 2
EOF
)"
```
### 8. Create Tag ### 8. Create Tag
> **Executed by default**. Only skip this step when user explicitly inputs "skip" or "skip tag". > **Executed by default**. Only skip this step when user explicitly inputs "skip" or "skip tag".
@@ -228,7 +234,7 @@ Examples (multi-line commit, more recommended):
- **Create tag by default**: Unless user inputs "skip" or "skip tag", create tag by default. - **Create tag by default**: Unless user inputs "skip" or "skip tag", create tag by default.
- **Update version before commit**: First determine version number and modify version file, then commit at once, avoid using `--amend`. - **Update version before commit**: First determine version number and modify version file, then commit at once, avoid using `--amend`.
- **Version file must be verified**: After updating version number and `git add`, must run `git diff --cached --name-only` to confirm version file is in staging area before executing commit. - **Version file must be verified**: After updating version number and `git add`, must run `git diff --cached --name-only` to confirm version file is in staging area before executing commit.
- **Windows encoding issues**: Cursor Shell tool garbles Chinese parameters, must use **English** commit messages. For Chinese, please manually execute in terminal. - **Commit message language**: Default use Chinese (macOS/Linux); Windows must use English due to Cursor Shell tool encoding issues.
- **monorepo scope**: If staging area only affects single subproject, commit message should have scope; if affecting multiple subprojects, no scope needed. - **monorepo scope**: If staging area only affects single subproject, commit message should have scope; if affecting multiple subprojects, no scope needed.
- **monorepo tag format**: Use `<subproject>-<version>` format (e.g., `ios-0.1.0`, `android-1.1.0`). - **monorepo tag format**: Use `<subproject>-<version>` format (e.g., `ios-0.1.0`, `android-1.1.0`).
- **No version file case**: If project type cannot be identified or no version file (like pure Go project), only create git tag, don't update any files. - **No version file case**: If project type cannot be identified or no version file (like pure Go project), only create git tag, don't update any files.

View File

@@ -130,9 +130,13 @@ Based on changes in staging area, **analyze and generate meaningful commit messa
- Use [Conventional Commits](https://www.conventionalcommits.org/) specification - Use [Conventional Commits](https://www.conventionalcommits.org/) specification
- Commit message should concisely but accurately describe "why" rather than just "what" - Commit message should concisely but accurately describe "why" rather than just "what"
- Common types: `feat` (new feature), `fix` (fix), `docs` (documentation), `refactor` (refactoring), `chore` (miscellaneous), `test` (test), etc. - Common types: `feat` (new feature), `fix` (fix), `docs` (documentation), `refactor` (refactoring), `chore` (miscellaneous), `test` (test), etc.
- **Language selection**:
- **Default (macOS/Linux)**: Use Chinese (中文) for commit messages
- **Windows**: Use English due to encoding issues with Cursor Shell tool
- **If monorepo and this commit only affects single subproject, include subproject name in commit message**: - **If monorepo and this commit only affects single subproject, include subproject name in commit message**:
- Format: `<type>(<scope>): <description>`, where `<scope>` is subproject name - Format: `<type>(<scope>): <description>`, where `<scope>` is subproject name
- Example: `feat(ios): support OGG Opus upload` or `fix(electron): fix clipboard injection failure` - Example (Chinese): `feat(ios): 支持 OGG Opus 上传` or `fix(electron): 修复剪贴板注入失败`
- Example (English): `feat(ios): support OGG Opus upload` or `fix(electron): fix clipboard injection failure`
- If affecting multiple subprojects or entire repository, no need to add scope - If affecting multiple subprojects or entire repository, no need to add scope
### 6. Update Project Version Number ### 6. Update Project Version Number
@@ -149,11 +153,27 @@ Based on changes in staging area, **analyze and generate meaningful commit messa
Execute commit with generated commit message (staging area now includes user's changes and version number update). Execute commit with generated commit message (staging area now includes user's changes and version number update).
**Windows encoding issue solution:** **Commit message language by platform:**
Cursor's Shell tool has encoding issues when passing Chinese parameters on Windows, causing garbled Git commit messages. **macOS/Linux** (use Chinese commit messages):
**Correct approach**: Use **English** commit message ```bash
# Single line commit
git commit -m "feat(android): 添加新功能"
# Multi-line commit (recommended)
git commit -m "$(cat <<'EOF'
feat(android): 添加新功能
- 详细说明 1
- 详细说明 2
EOF
)"
```
**Windows** (use English commit messages):
Due to Cursor's Shell tool encoding issues on Windows, **must use English** commit messages.
```powershell ```powershell
# Single line commit # Single line commit
@@ -163,26 +183,12 @@ git commit -m "feat(android): add new feature"
git commit --message="fix(android): fix code review issues" --message="- Fix syntax error" --message="- Use JSONObject instead of manual JSON" git commit --message="fix(android): fix code review issues" --message="- Fix syntax error" --message="- Use JSONObject instead of manual JSON"
``` ```
**Prohibited methods** (will cause garbled text): **Windows prohibited methods** (will cause garbled text):
- No Chinese commit messages - Cursor Shell tool encoding error when passing Chinese parameters - No Chinese commit messages - Cursor Shell tool encoding error when passing Chinese parameters
- No `Out-File -Encoding utf8` - writes UTF-8 with BOM - No `Out-File -Encoding utf8` - writes UTF-8 with BOM
- No Write tool to write temp files - encoding uncontrollable - No Write tool to write temp files - encoding uncontrollable
- No PowerShell here-string `@"..."@` - newline parsing issues - No PowerShell here-string `@"..."@` - newline parsing issues
**For Chinese commit messages**: Please manually execute `git commit` in terminal, or use Git GUI tools other than Cursor.
**macOS/Linux alternative** (supports Chinese):
```bash
git commit -m "$(cat <<'EOF'
feat(android): new feature description
- Detail 1
- Detail 2
EOF
)"
```
### 8. Create Tag ### 8. Create Tag
> **Executed by default**. Only skip this step when user explicitly inputs "skip" or "skip tag". > **Executed by default**. Only skip this step when user explicitly inputs "skip" or "skip tag".
@@ -247,7 +253,7 @@ git push origin <subproject>-<version>
- **Create tag by default**: Unless user inputs "skip" or "skip tag", create and push tag by default. - **Create tag by default**: Unless user inputs "skip" or "skip tag", create and push tag by default.
- **Update version before commit**: First determine version number and modify version file, then commit at once, avoid using `--amend`. - **Update version before commit**: First determine version number and modify version file, then commit at once, avoid using `--amend`.
- **Version file must be verified**: After updating version number and `git add`, must run `git diff --cached --name-only` to confirm version file is in staging area before executing commit. - **Version file must be verified**: After updating version number and `git add`, must run `git diff --cached --name-only` to confirm version file is in staging area before executing commit.
- **Windows encoding issues**: Cursor Shell tool garbles Chinese parameters, must use **English** commit messages. For Chinese, please manually execute in terminal. - **Commit message language**: Default use Chinese (macOS/Linux); Windows must use English due to Cursor Shell tool encoding issues.
- **monorepo scope**: If staging area only affects single subproject, commit message should have scope; if affecting multiple subprojects, no scope needed. - **monorepo scope**: If staging area only affects single subproject, commit message should have scope; if affecting multiple subprojects, no scope needed.
- **monorepo tag format**: Use `<subproject>-<version>` format (e.g., `ios-0.1.0`, `android-1.1.0`). - **monorepo tag format**: Use `<subproject>-<version>` format (e.g., `ios-0.1.0`, `android-1.1.0`).
- **No version file case**: If project type cannot be identified or no version file (like pure Go project), only create git tag, don't update any files. - **No version file case**: If project type cannot be identified or no version file (like pure Go project), only create git tag, don't update any files.

View File

@@ -1,14 +1,20 @@
--- ---
description: Build and release Android APK to Gitea description: Build and release Android APK to Gitea (generic for any Android project)
--- ---
# Android Release Command # Android Release Command
Build Android APK and upload to Gitea Release. Build Android APK and upload to Gitea Release for the current directory's Android project.
## Prerequisites Check ## Prerequisites
First, verify the environment: Before running this command:
1. **Create and push the git tag first** (e.g., `myapp-1.0.0`, `android-1.0.0`, or `v1.0.0`)
2. Ensure `GITEA_TOKEN` environment variable is set
3. Ensure Android project exists in current directory
Check the environment:
```bash ```bash
# Check GITEA_TOKEN # Check GITEA_TOKEN
@@ -17,40 +23,25 @@ echo "GITEA_TOKEN: ${GITEA_TOKEN:+SET}"
# Check current directory for Android project # Check current directory for Android project
ls -la app/build.gradle.kts 2>/dev/null || ls -la android/app/build.gradle.kts 2>/dev/null || echo "No Android project found" ls -la app/build.gradle.kts 2>/dev/null || ls -la android/app/build.gradle.kts 2>/dev/null || echo "No Android project found"
# Check current version # Check recent tags (script will use the latest tag)
grep -h 'versionName' app/build.gradle.kts android/app/build.gradle.kts 2>/dev/null | head -1 git tag -l | tail -10
# Check existing tags
git tag -l '*android*' -l 'v*' | tail -5
``` ```
## Release Process
**If there are uncommitted changes:**
1. Increment version: update `versionCode` (+1) and `versionName` in `app/build.gradle.kts`
2. Commit the changes with a descriptive message
3. Create annotated git tag:
- Monorepo: `git tag -a android-{version} -m "Release notes"`
- Standalone: `git tag -a v{version} -m "Release notes"`
4. Push commit and tag: `git push && git push origin {tag}`
**If no uncommitted changes but tag doesn't exist:**
1. Create tag and push it
## Build and Upload ## Build and Upload
After tag is created and pushed, run: After creating and pushing the tag, run:
```bash ```bash
node ~/.config/opencode/bin/release-android.mjs node ~/.opencode/bin/release-android.mjs
``` ```
The script will: The script will:
- Auto-detect Android project root (standalone or monorepo) - Auto-detect Android project root (standalone or monorepo)
- Auto-detect project name from recent git tags or directory name
- Auto-detect Gitea config from git remote URL (HTTPS/SSH) - Auto-detect Gitea config from git remote URL (HTTPS/SSH)
- Auto-detect Java from Android Studio - Auto-detect Java from Android Studio
- Build release APK - Build release APK
- Upload to Gitea Release - Upload to Gitea Release matching the tag pattern
## Error Handling ## Error Handling

View File

@@ -48,13 +48,24 @@ git add command/ skill/ opencode.json
Generate concise commit message based on change content: Generate concise commit message based on change content:
- Use [Conventional Commits](https://www.conventionalcommits.org/) specification - Use [Conventional Commits](https://www.conventionalcommits.org/) specification
- **Language selection**:
- **Default (macOS/Linux)**: Use Chinese (中文) for commit messages
- **Windows**: Use English due to encoding issues with Cursor Shell tool
- Common types: - Common types:
- `feat`: New command or config - `feat`: New command or config
- `fix`: Fix command or config issues - `fix`: Fix command or config issues
- `docs`: Documentation update - `docs`: Documentation update
- `chore`: Miscellaneous adjustments - `chore`: Miscellaneous adjustments
**Examples**: **Examples (macOS/Linux - Chinese)**:
```bash
git commit -m "feat: 添加 Vue.js 开发命令"
git commit -m "fix: 修正 MCP 服务器配置"
git commit -m "docs: 更新 review 命令说明"
```
**Examples (Windows - English)**:
```bash ```bash
git commit -m "feat: add new developer command for Vue.js" git commit -m "feat: add new developer command for Vue.js"
@@ -72,4 +83,4 @@ git push origin main
- **Only sync config files**: Only add `command/`, `skill/` and `opencode.json`, don't commit other local data. - **Only sync config files**: Only add `command/`, `skill/` and `opencode.json`, don't commit other local data.
- **Sensitive info**: `opencode.json` may contain API keys, ensure remote repository access permissions are set correctly. - **Sensitive info**: `opencode.json` may contain API keys, ensure remote repository access permissions are set correctly.
- **English commit**: To avoid encoding issues, suggest using English commit messages. - **Commit message language**: Default use Chinese (macOS/Linux); Windows must use English due to Cursor Shell tool encoding issues.