chore: merge remote changes and resolve path conflicts
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
package.json
|
||||||
|
bun.lock
|
||||||
|
*.log
|
||||||
|
env.sh
|
||||||
12
AGENTS.md
Normal file
12
AGENTS.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# 全局规则
|
||||||
|
|
||||||
|
## 语言偏好
|
||||||
|
|
||||||
|
**重要:请始终使用中文(简体中文)与用户交流。**
|
||||||
|
|
||||||
|
所有回复、解释、错误消息、建议和对话都应该使用中文,除非:
|
||||||
|
- 代码注释需要使用英文(根据项目规范)
|
||||||
|
- 用户明确要求使用其他语言
|
||||||
|
- 技术术语在中文中没有合适的翻译(可以中英文混用,如 "React Hooks")
|
||||||
|
|
||||||
|
这是用户的个人偏好设置,适用于所有项目。
|
||||||
126
README.md
Normal file
126
README.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# OpenCode Configuration
|
||||||
|
|
||||||
|
This repository contains custom OpenCode configuration including commands, skills, and MCP services.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── opencode.json # MCP services configuration
|
||||||
|
├── command/ # Custom commands
|
||||||
|
│ ├── auto-commit.md
|
||||||
|
│ ├── commit-push.md
|
||||||
|
│ ├── create-gitea-repo.md
|
||||||
|
│ ├── release-android.md
|
||||||
|
│ ├── review.md
|
||||||
|
│ ├── sync-oc-pull.md
|
||||||
|
│ └── sync-oc-push.md
|
||||||
|
└── skill/ # Agent skills
|
||||||
|
├── android-developer/
|
||||||
|
├── electron-developer/
|
||||||
|
├── go-developer/
|
||||||
|
└── ios-developer/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
### 1. Clone Configuration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.digitevents.com/ai/opencode.git ~/.config/opencode
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure Environment Variables
|
||||||
|
|
||||||
|
Create `env.sh` file (not tracked in git) or use the one from iCloud:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Option 1: Create manually
|
||||||
|
cat > ~/.config/opencode/env.sh << 'EOF'
|
||||||
|
# OpenCode Environment Variables
|
||||||
|
export REF_API_KEY="your-ref-api-key"
|
||||||
|
export FIGMA_API_KEY="your-figma-api-key"
|
||||||
|
export GITEA_API_TOKEN="your-gitea-token"
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Option 2: Use iCloud (macOS only)
|
||||||
|
# File location: ~/Library/Mobile Documents/com~apple~CloudDocs/opencode-env.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Load Environment Variables
|
||||||
|
|
||||||
|
Add to your `~/.zshrc` or `~/.bashrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Load from iCloud (macOS)
|
||||||
|
[[ -f ~/Library/Mobile\ Documents/com~apple~CloudDocs/opencode-env.sh ]] && \
|
||||||
|
source ~/Library/Mobile\ Documents/com~apple~CloudDocs/opencode-env.sh
|
||||||
|
|
||||||
|
# Or load from local file
|
||||||
|
[[ -f ~/.config/opencode/env.sh ]] && source ~/.config/opencode/env.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Then reload your shell:
|
||||||
|
```bash
|
||||||
|
source ~/.zshrc
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
Run custom commands in OpenCode TUI:
|
||||||
|
|
||||||
|
- `/auto-commit` - Auto-generate commit and create tag
|
||||||
|
- `/commit-push` - Commit, tag and push to remote
|
||||||
|
- `/create-gitea-repo` - Create repository on Gitea
|
||||||
|
- `/release-android` - Build and release Android APK to Gitea
|
||||||
|
- `/sync-oc-pull` - Pull OpenCode config changes
|
||||||
|
- `/sync-oc-push` - Push OpenCode config changes
|
||||||
|
- `/review` - Review code or documentation
|
||||||
|
|
||||||
|
### Skills
|
||||||
|
|
||||||
|
Skills are automatically loaded by agents when needed:
|
||||||
|
|
||||||
|
- `android-developer` - Android development with Kotlin/Compose
|
||||||
|
- `electron-developer` - Electron desktop app development
|
||||||
|
- `ios-developer` - iOS development with Swift/SwiftUI
|
||||||
|
- `go-developer` - Go backend development
|
||||||
|
|
||||||
|
### MCP Services
|
||||||
|
|
||||||
|
Configure MCP services in `opencode.json`:
|
||||||
|
|
||||||
|
- `ref` - Ref.tools API (requires `REF_API_KEY`)
|
||||||
|
- `figma` - Figma Developer MCP (requires `FIGMA_API_KEY`)
|
||||||
|
|
||||||
|
## Sync Across Devices
|
||||||
|
|
||||||
|
### Push Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/.config/opencode
|
||||||
|
git add .
|
||||||
|
git commit -m "chore: update config"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pull Changes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/.config/opencode
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use the built-in commands:
|
||||||
|
- `/sync-oc-push` - Push changes
|
||||||
|
- `/sync-oc-pull` - Pull changes
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
**Important**: Never commit sensitive information like API keys to git.
|
||||||
|
|
||||||
|
- `env.sh` is ignored by git
|
||||||
|
- Store credentials in iCloud or use a secure password manager
|
||||||
|
- Use environment variables instead of hardcoding secrets
|
||||||
494
bin/release-android.mjs
Executable file
494
bin/release-android.mjs
Executable file
@@ -0,0 +1,494 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Android Release Script - Universal Version
|
||||||
|
*
|
||||||
|
* Automatically builds Android APK and uploads to Gitea Release.
|
||||||
|
*
|
||||||
|
* Features:
|
||||||
|
* - Auto-detect Android project root (standalone or monorepo)
|
||||||
|
* - Auto-detect Gitea config from git remote URL (HTTPS/SSH)
|
||||||
|
* - Auto-detect Java Home from Android Studio
|
||||||
|
* - Support tag-based release workflow
|
||||||
|
*
|
||||||
|
* Environment Variables:
|
||||||
|
* - GITEA_TOKEN (required): Gitea API token
|
||||||
|
* - GITEA_API_URL (optional): Override Gitea API URL
|
||||||
|
* - GITEA_OWNER (optional): Override repository owner
|
||||||
|
* - GITEA_REPO (optional): Override repository name
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* node release-android.mjs [android-dir]
|
||||||
|
*
|
||||||
|
* If android-dir is not specified, will auto-detect from current directory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Project Detection
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find Android project root directory
|
||||||
|
* Looks for app/build.gradle.kts or build.gradle.kts with android plugin
|
||||||
|
*/
|
||||||
|
function findAndroidRoot(startDir) {
|
||||||
|
const candidates = [
|
||||||
|
startDir, // Current dir is Android root
|
||||||
|
path.join(startDir, 'android'), // Monorepo: ./android/
|
||||||
|
path.join(startDir, '..'), // Parent dir
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const dir of candidates) {
|
||||||
|
const buildGradle = path.join(dir, 'app/build.gradle.kts');
|
||||||
|
const buildGradleGroovy = path.join(dir, 'app/build.gradle');
|
||||||
|
|
||||||
|
if (fs.existsSync(buildGradle) || fs.existsSync(buildGradleGroovy)) {
|
||||||
|
return path.resolve(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find git repository root
|
||||||
|
*/
|
||||||
|
function findGitRoot(startDir) {
|
||||||
|
try {
|
||||||
|
const result = execSync('git rev-parse --show-toplevel', {
|
||||||
|
cwd: startDir,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
shell: true,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
}).trim();
|
||||||
|
return result;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect if project is a monorepo (Android is a subdirectory)
|
||||||
|
*/
|
||||||
|
function isMonorepo(androidDir, gitRoot) {
|
||||||
|
if (!gitRoot) return false;
|
||||||
|
return path.resolve(androidDir) !== path.resolve(gitRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Git/Gitea Configuration
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse git remote URL to extract Gitea configuration
|
||||||
|
* Supports both HTTPS and SSH formats
|
||||||
|
*/
|
||||||
|
function detectGiteaConfig(gitRoot) {
|
||||||
|
try {
|
||||||
|
const remoteUrl = execSync('git remote get-url origin', {
|
||||||
|
cwd: gitRoot,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
shell: true,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
}).trim();
|
||||||
|
|
||||||
|
// HTTPS format: https://git.example.com/owner/repo.git
|
||||||
|
const httpsMatch = remoteUrl.match(/https?:\/\/([^/]+)\/([^/]+)\/([^/]+?)(\.git)?$/);
|
||||||
|
if (httpsMatch) {
|
||||||
|
return {
|
||||||
|
apiUrl: `https://${httpsMatch[1]}`,
|
||||||
|
owner: httpsMatch[2],
|
||||||
|
repo: httpsMatch[3]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// SSH format: git@git.example.com:owner/repo.git
|
||||||
|
const sshMatch = remoteUrl.match(/git@([^:]+):([^/]+)\/([^/]+?)(\.git)?$/);
|
||||||
|
if (sshMatch) {
|
||||||
|
return {
|
||||||
|
apiUrl: `https://${sshMatch[1]}`,
|
||||||
|
owner: sshMatch[2],
|
||||||
|
repo: sshMatch[3]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore errors
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Java Detection
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect Java Home, prioritizing Android Studio's bundled JDK
|
||||||
|
*/
|
||||||
|
function detectJavaHome() {
|
||||||
|
const possiblePaths = [
|
||||||
|
// macOS - Android Studio bundled JDK
|
||||||
|
'/Applications/Android Studio.app/Contents/jbr/Contents/Home',
|
||||||
|
'/Applications/Android Studio.app/Contents/jre/Contents/Home',
|
||||||
|
// Linux - Android Studio bundled JDK
|
||||||
|
`${process.env.HOME}/android-studio/jbr`,
|
||||||
|
`${process.env.HOME}/android-studio/jre`,
|
||||||
|
// Environment variable
|
||||||
|
process.env.JAVA_HOME,
|
||||||
|
// Common system paths
|
||||||
|
'/usr/lib/jvm/java-11-openjdk',
|
||||||
|
'/usr/lib/jvm/java-17-openjdk',
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const javaHome of possiblePaths) {
|
||||||
|
if (javaHome && fs.existsSync(javaHome)) {
|
||||||
|
const javaBin = path.join(javaHome, 'bin', 'java');
|
||||||
|
if (fs.existsSync(javaBin)) {
|
||||||
|
return javaHome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Version & Tag Management
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read version from build.gradle.kts
|
||||||
|
*/
|
||||||
|
function readVersion(androidDir) {
|
||||||
|
const buildGradlePath = path.join(androidDir, 'app/build.gradle.kts');
|
||||||
|
const buildGradleGroovyPath = path.join(androidDir, 'app/build.gradle');
|
||||||
|
|
||||||
|
let buildGradle;
|
||||||
|
if (fs.existsSync(buildGradlePath)) {
|
||||||
|
buildGradle = fs.readFileSync(buildGradlePath, 'utf-8');
|
||||||
|
} else if (fs.existsSync(buildGradleGroovyPath)) {
|
||||||
|
buildGradle = fs.readFileSync(buildGradleGroovyPath, 'utf-8');
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionMatch = buildGradle.match(/versionName\s*[=:]\s*["']([^"']+)["']/);
|
||||||
|
return versionMatch ? versionMatch[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tag name based on project structure
|
||||||
|
*/
|
||||||
|
function getTagName(version, isMonorepoProject) {
|
||||||
|
// Monorepo uses prefixed tag, standalone uses v-prefix
|
||||||
|
return isMonorepoProject ? `android-${version}` : `v${version}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get tag information (check existence and get annotation)
|
||||||
|
*/
|
||||||
|
function getTagInfo(tagName, gitRoot) {
|
||||||
|
try {
|
||||||
|
const tagExists = execSync(`git tag -l ${tagName}`, {
|
||||||
|
cwd: gitRoot,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
shell: true,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
}).trim();
|
||||||
|
|
||||||
|
if (!tagExists) {
|
||||||
|
return { exists: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get tag annotation
|
||||||
|
const annotation = execSync(`git tag -l --format='%(contents)' ${tagName}`, {
|
||||||
|
cwd: gitRoot,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
shell: true,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
}).trim();
|
||||||
|
|
||||||
|
if (annotation) {
|
||||||
|
return { exists: true, message: annotation };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to commit message
|
||||||
|
const commitMsg = execSync(`git log -1 --format=%B ${tagName}`, {
|
||||||
|
cwd: gitRoot,
|
||||||
|
encoding: 'utf-8',
|
||||||
|
shell: true,
|
||||||
|
stdio: ['pipe', 'pipe', 'pipe']
|
||||||
|
}).trim();
|
||||||
|
|
||||||
|
return { exists: true, message: commitMsg || `Release ${tagName}` };
|
||||||
|
} catch {
|
||||||
|
return { exists: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Build & Upload
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Android APK
|
||||||
|
*/
|
||||||
|
function buildApk(androidDir, javaHome) {
|
||||||
|
console.log('Building APK...');
|
||||||
|
execSync('./gradlew assembleRelease --quiet', {
|
||||||
|
cwd: androidDir,
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
env: { ...process.env, JAVA_HOME: javaHome }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find built APK file
|
||||||
|
*/
|
||||||
|
function findApk(androidDir) {
|
||||||
|
const signedApkPath = path.join(androidDir, 'app/build/outputs/apk/release/app-release.apk');
|
||||||
|
const unsignedApkPath = path.join(androidDir, 'app/build/outputs/apk/release/app-release-unsigned.apk');
|
||||||
|
|
||||||
|
if (fs.existsSync(signedApkPath)) {
|
||||||
|
return { path: signedApkPath, signed: true };
|
||||||
|
} else if (fs.existsSync(unsignedApkPath)) {
|
||||||
|
return { path: unsignedApkPath, signed: false };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute curl and return JSON response
|
||||||
|
*/
|
||||||
|
function curlJson(cmd) {
|
||||||
|
try {
|
||||||
|
const result = execSync(cmd, { encoding: 'utf-8', shell: true, stdio: ['pipe', 'pipe', 'pipe'] });
|
||||||
|
return JSON.parse(result);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload APK to Gitea Release
|
||||||
|
*/
|
||||||
|
function uploadToGitea(config) {
|
||||||
|
const { apiUrl, owner, repo, token, tagName, fileName, apkPath, releaseMessage } = config;
|
||||||
|
|
||||||
|
const repoApiBase = `${apiUrl}/api/v1/repos/${owner}/${repo}`;
|
||||||
|
|
||||||
|
// Get or create release
|
||||||
|
let releaseId;
|
||||||
|
const releases = curlJson(
|
||||||
|
`curl -s -H "Authorization: token ${token}" "${repoApiBase}/releases"`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Array.isArray(releases)) {
|
||||||
|
const existingRelease = releases.find((r) => r.tag_name === tagName);
|
||||||
|
if (existingRelease) {
|
||||||
|
releaseId = existingRelease.id;
|
||||||
|
console.log(`Found existing 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}`);
|
||||||
|
execSync(
|
||||||
|
`curl -s -X DELETE -H "Authorization: token ${token}" "${repoApiBase}/releases/${releaseId}/assets/${existingAsset.id}"`,
|
||||||
|
{ shell: true, stdio: ['pipe', 'pipe', 'pipe'] }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!releaseId) {
|
||||||
|
console.log('Creating new Release...');
|
||||||
|
const releaseData = {
|
||||||
|
tag_name: tagName,
|
||||||
|
name: `Android APK ${tagName}`,
|
||||||
|
body: releaseMessage,
|
||||||
|
draft: false,
|
||||||
|
prerelease: false
|
||||||
|
};
|
||||||
|
|
||||||
|
const tempJsonPath = path.join(process.cwd(), '.release-data.json');
|
||||||
|
fs.writeFileSync(tempJsonPath, JSON.stringify(releaseData));
|
||||||
|
|
||||||
|
const releaseInfo = curlJson(
|
||||||
|
`curl -s -X POST "${repoApiBase}/releases" \
|
||||||
|
-H "Authorization: token ${token}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d @${tempJsonPath}`
|
||||||
|
);
|
||||||
|
|
||||||
|
fs.unlinkSync(tempJsonPath);
|
||||||
|
|
||||||
|
if (!releaseInfo || !releaseInfo.id) {
|
||||||
|
throw new Error(`Failed to create release: ${JSON.stringify(releaseInfo)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
releaseId = releaseInfo.id;
|
||||||
|
console.log(`Release created (ID: ${releaseId})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Upload APK
|
||||||
|
console.log('Uploading APK...');
|
||||||
|
const uploadUrl = `${repoApiBase}/releases/${releaseId}/assets?name=${encodeURIComponent(fileName)}`;
|
||||||
|
const uploadResult = curlJson(
|
||||||
|
`curl -s -X POST "${uploadUrl}" \
|
||||||
|
-H "Authorization: token ${token}" \
|
||||||
|
-F "attachment=@${apkPath}"`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!uploadResult || !uploadResult.id) {
|
||||||
|
throw new Error(`Failed to upload APK: ${JSON.stringify(uploadResult)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
releaseUrl: `${apiUrl}/${owner}/${repo}/releases/tag/${tagName}`,
|
||||||
|
fileSize: uploadResult.size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const argDir = process.argv[2];
|
||||||
|
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
console.log('Android Release Script');
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
|
||||||
|
// 1. Find Android project root
|
||||||
|
const searchDir = argDir ? path.resolve(cwd, argDir) : cwd;
|
||||||
|
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]');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Android project: ${androidDir}`);
|
||||||
|
|
||||||
|
// 2. Find git root
|
||||||
|
const gitRoot = findGitRoot(androidDir);
|
||||||
|
if (!gitRoot) {
|
||||||
|
console.error('Error: Not a git repository');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Git root: ${gitRoot}`);
|
||||||
|
|
||||||
|
const monorepo = isMonorepo(androidDir, gitRoot);
|
||||||
|
console.log(`Project type: ${monorepo ? 'Monorepo' : 'Standalone'}`);
|
||||||
|
|
||||||
|
// 3. Detect Gitea configuration
|
||||||
|
const detected = detectGiteaConfig(gitRoot);
|
||||||
|
const GITEA_TOKEN = process.env.GITEA_TOKEN || '';
|
||||||
|
const GITEA_API_URL = process.env.GITEA_API_URL || detected.apiUrl || '';
|
||||||
|
const GITEA_OWNER = process.env.GITEA_OWNER || detected.owner || '';
|
||||||
|
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"');
|
||||||
|
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');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Gitea: ${GITEA_API_URL}/${GITEA_OWNER}/${GITEA_REPO}`);
|
||||||
|
|
||||||
|
// 4. Read version
|
||||||
|
const version = readVersion(androidDir);
|
||||||
|
if (!version) {
|
||||||
|
console.error('Error: Cannot read versionName from build.gradle');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Version: ${version}`);
|
||||||
|
|
||||||
|
// 5. Check tag
|
||||||
|
const tagName = getTagName(version, monorepo);
|
||||||
|
const tagInfo = getTagInfo(tagName, gitRoot);
|
||||||
|
|
||||||
|
if (!tagInfo.exists) {
|
||||||
|
console.error(`Error: Git tag "${tagName}" not found`);
|
||||||
|
console.error('');
|
||||||
|
console.error('Please create a tag before releasing:');
|
||||||
|
console.error(` git tag -a ${tagName} -m "Release notes"`);
|
||||||
|
console.error(` git push origin ${tagName}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Tag: ${tagName}`);
|
||||||
|
|
||||||
|
// 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');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`Java: ${javaHome}`);
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('Building...');
|
||||||
|
|
||||||
|
// 7. Build APK
|
||||||
|
try {
|
||||||
|
buildApk(androidDir, javaHome);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Build failed:', 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/');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log(`APK: ${apk.path} (${apk.signed ? 'signed' : 'unsigned'})`);
|
||||||
|
|
||||||
|
// 9. Upload to Gitea
|
||||||
|
const fileName = `${GITEA_REPO}-android-${version}.apk`;
|
||||||
|
|
||||||
|
console.log('');
|
||||||
|
console.log('Uploading to Gitea...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = uploadToGitea({
|
||||||
|
apiUrl: GITEA_API_URL,
|
||||||
|
owner: GITEA_OWNER,
|
||||||
|
repo: GITEA_REPO,
|
||||||
|
token: GITEA_TOKEN,
|
||||||
|
tagName,
|
||||||
|
fileName,
|
||||||
|
apkPath: apk.path,
|
||||||
|
releaseMessage: tagInfo.message
|
||||||
|
});
|
||||||
|
|
||||||
|
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(`URL: ${result.releaseUrl}`);
|
||||||
|
console.log('='.repeat(60));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Upload failed:', err.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
Reference in New Issue
Block a user