Spaces:
Runtime error
Runtime error
| 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`); | |
| }); | |