yasarefe's picture
Fix APK build system: remove npm run build, add Gradle and Android SDK packages
4c632a6
const express = require('express');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const { v4: uuidv4 } = require('uuid');
const app = express();
const PORT = 7860;
// CORS middleware
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
// JSON body parser
app.use(express.json());
// Job storage
const jobs = {};
// Web arayüzü
app.get('/', (req, res) => {
res.send(`
<html>
<head>
<title>APK Build Service</title>
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
#status { margin-top: 20px; padding: 10px; border: 1px solid #ccc; }
</style>
</head>
<body>
<h1>APK Build Service API</h1>
<p>POST /build - Build başlat (appName, packageId, repoUrl)</p>
<p>GET /status/:jobId - Durum sorgula</p>
<p>GET /download/:jobId - APK indir</p>
</body>
</html>
`);
});
// Build endpoint
app.post('/build', (req, res) => {
const { appName, packageId, repoUrl } = req.body;
if (!appName || !packageId || !repoUrl) {
return res.status(400).json({ success: false, message: 'appName, packageId ve repoUrl gerekli' });
}
const jobId = uuidv4();
jobs[jobId] = {
status: 'pending',
appName,
packageId,
repoUrl,
createdAt: new Date(),
apkPath: null
};
// Build işlemini arka planda başlat
buildAPK(jobId);
res.json({ success: true, jobId, message: 'Build başlatıldı' });
});
// Durum sorgulama
app.get('/status/:jobId', (req, res) => {
const { jobId } = req.params;
const job = jobs[jobId];
if (!job) {
return res.status(404).json({ success: false, message: 'Job bulunamadı' });
}
res.json({
success: true,
jobId,
status: job.status,
appName: job.appName,
createdAt: job.createdAt
});
});
// APK indirme
app.get('/download/:jobId', (req, res) => {
const { jobId } = req.params;
const job = jobs[jobId];
if (!job) {
return res.status(404).json({ success: false, message: 'Job bulunamadı' });
}
if (job.status !== 'completed') {
return res.status(400).json({ success: false, message: 'APK henüz hazır değil' });
}
if (job.apkPath && fs.existsSync(job.apkPath)) {
res.download(job.apkPath, `${job.appName}-debug.apk`);
} else {
res.status(404).json({ success: false, message: 'APK dosyası bulunamadı' });
}
});
// Tüm job'ları listele
app.get('/jobs', (req, res) => {
const jobList = Object.entries(jobs).map(([jobId, job]) => ({
jobId,
status: job.status,
appName: job.appName,
packageId: job.packageId,
repoUrl: job.repoUrl,
createdAt: job.createdAt,
error: job.error
}));
// Tarihe göre sırala (en yeni önce)
jobList.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
res.json({ success: true, jobs: jobList });
});
// Build fonksiyonu
function buildAPK(jobId) {
const job = jobs[jobId];
job.status = 'building';
const buildDir = `/tmp/build-${jobId}`;
// Temizle
if (fs.existsSync(buildDir)) {
fs.rmSync(buildDir, { recursive: true });
}
// Klonla (with token if available)
const token = process.env.GITHUB_TOKEN;
let cloneUrl = job.repoUrl;
if (token && job.repoUrl.startsWith('https://github.com/')) {
cloneUrl = job.repoUrl.replace('https://github.com/', `https://git:${token}@github.com/`);
}
exec(`git clone ${cloneUrl} ${buildDir}`, (error, stdout, stderr) => {
if (error) {
console.error('Klonlama hatası:', error);
job.status = 'failed';
job.error = error.message;
return;
}
// Build işlemi (step by step) - WebView Android App
const steps = [
'npm install',
// Android projesi oluştur
`mkdir -p android-project/app/src/main/java/com/example/webview`,
`mkdir -p android-project/app/src/main/res/values`,
`mkdir -p android-project/app/src/main/res/layout`,
// AndroidManifest.xml oluştur
`cat > android-project/app/src/main/AndroidManifest.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.webview">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="${job.appName}"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
EOF`,
// MainActivity.java oluştur
`cat > android-project/app/src/main/java/com/example/webview/MainActivity.java << 'EOF'
package com.example.webview;
import android.app.Activity;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WebView webView = findViewById(R.id.webView);
webView.setWebViewClient(new WebViewClient());
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setDomStorageEnabled(true);
webView.loadUrl("file:///android_asset/index.html");
}
}
EOF`,
// activity_main.xml oluştur
`cat > android-project/app/src/main/res/layout/activity_main.xml << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<WebView
android:id="@+id/webView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
EOF`,
// strings.xml oluştur
`cat > android-project/app/src/main/res/values/strings.xml << 'EOF'
<resources>
<string name="app_name">${job.appName}</string>
</resources>
EOF`,
// build.gradle oluştur
`cat > android-project/app/build.gradle << 'EOF'
plugins {
id 'com.android.application'
}
android {
namespace 'com.example.webview'
compileSdk 34
defaultConfig {
applicationId "com.example.webview"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
}
EOF`,
// settings.gradle oluştur
`cat > android-project/settings.gradle << 'EOF'
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "${job.appName}"
include ':app'
EOF`,
// Web dosyalarını assets klasörüne kopyala
`mkdir -p android-project/app/src/main/assets`,
`cp -r index.html css js android-project/app/src/main/assets/ 2>/dev/null || cp -r * android-project/app/src/main/assets/`,
// Gradle wrapper oluştur
`cd android-project && gradle wrapper`,
// APK derle
`cd android-project && ./gradlew assembleDebug`
];
let currentStep = 0;
const runStep = () => {
if (currentStep >= steps.length) {
// Tüm adımlar başarılı
const apkPath = path.join(buildDir, 'android/app/build/outputs/apk/debug/app-debug.apk');
const destPath = `/tmp/${job.appName}-${jobId}-debug.apk`;
if (fs.existsSync(apkPath)) {
fs.copyFileSync(apkPath, destPath);
console.log('APK oluşturuldu:', destPath);
job.status = 'completed';
job.apkPath = destPath;
} else {
job.status = 'failed';
job.error = 'APK dosyası bulunamadı';
}
return;
}
const step = steps[currentStep];
console.log(`Step ${currentStep + 1}: ${step}`);
exec(step, { cwd: buildDir, timeout: 600000 }, (err, stdout, stderr) => {
if (err) {
console.error(`Step ${currentStep + 1} failed:`, err.message);
job.status = 'failed';
job.error = `Step ${currentStep + 1} failed: ${err.message}`;
return;
}
if (stdout) console.log(stdout);
if (stderr) console.log(stderr);
currentStep++;
runStep();
});
};
runStep();
});
}
app.listen(PORT, () => {
console.log(`APK Build Service ${PORT} portunda çalışıyor`);
});