UNPKG

native-update

Version:

Foundation package for building a comprehensive update system for Capacitor apps. Provides architecture and interfaces but requires backend implementation.

583 lines (467 loc) 14.6 kB
import chalk from 'chalk'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import { dirname } from 'path'; import ora from 'ora'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); export async function createBackend(type, options) { const spinner = ora(`Creating ${type} backend template...`).start(); try { const outputDir = path.resolve(options.output); // Check if directory exists try { await fs.access(outputDir); spinner.fail(`Directory ${outputDir} already exists`); return; } catch { // Directory doesn't exist, good } // Create output directory await fs.mkdir(outputDir, { recursive: true }); switch (type) { case 'express': await createExpressBackend(outputDir, options); break; case 'firebase': await createFirebaseBackend(outputDir, options); break; case 'vercel': await createVercelBackend(outputDir, options); break; default: throw new Error(`Unknown backend type: ${type}`); } spinner.succeed(`${type} backend created successfully!`); console.log(''); console.log(chalk.bold('Next steps:')); console.log(chalk.gray(` 1. cd ${options.output}`)); console.log(chalk.gray(` 2. npm install`)); console.log(chalk.gray(` 3. Configure your environment variables`)); console.log(chalk.gray(` 4. npm run dev`)); } catch (error) { spinner.fail(`Failed to create backend: ${error.message}`); process.exit(1); } } async function createExpressBackend(outputDir, options) { // Create package.json const packageJson = { name: "native-update-backend", version: "1.0.0", description: "Native Update backend server", type: "module", scripts: { dev: "node --watch server.js", start: "node server.js", test: "vitest" }, dependencies: { express: "^5.1.0", cors: "^2.8.5", "express-rate-limit": "^7.4.1", multer: "^1.4.5-lts.1", dotenv: "^16.4.7", jsonwebtoken: "^9.0.2", bcrypt: "^5.1.1" } }; if (options.withMonitoring) { packageJson.dependencies["@opentelemetry/api"] = "^1.9.0"; packageJson.dependencies["@opentelemetry/sdk-node"] = "^0.57.0"; packageJson.dependencies["winston"] = "^3.17.1"; } await fs.writeFile( path.join(outputDir, 'package.json'), JSON.stringify(packageJson, null, 2) ); // Create server.js const serverCode = `import express from 'express'; import cors from 'cors'; import dotenv from 'dotenv'; import { router as bundleRouter } from './routes/bundles.js'; import { router as authRouter } from './routes/auth.js'; ${options.withMonitoring ? "import { initMonitoring } from './monitoring/index.js';" : ''} dotenv.config(); const app = express(); const PORT = process.env.PORT || 3000; ${options.withMonitoring ? 'initMonitoring(app);' : ''} app.use(cors()); app.use(express.json()); // Routes app.use('/api/bundles', bundleRouter); app.use('/api/auth', authRouter); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); app.listen(PORT, () => { console.log(\`Server running on port \${PORT}\`); }); `; await fs.writeFile(path.join(outputDir, 'server.js'), serverCode); // Create routes directory await fs.mkdir(path.join(outputDir, 'routes'), { recursive: true }); // Create bundles route const bundlesRoute = `import express from 'express'; import multer from 'multer'; import path from 'path'; import fs from 'fs/promises'; import crypto from 'crypto'; const router = express.Router(); const upload = multer({ dest: 'uploads/' }); // Get latest bundle router.get('/latest', async (req, res) => { try { const { appId, version, channel = 'production' } = req.query; // TODO: Implement bundle lookup logic // This is a simplified example const bundle = { version: '1.0.1', url: \`\${req.protocol}://\${req.get('host')}/bundles/latest.zip\`, checksum: 'sha256:...', signature: 'signature...', metadata: { releaseNotes: 'Bug fixes and improvements' } }; res.json(bundle); } catch (error) { res.status(500).json({ error: error.message }); } }); // Upload new bundle router.post('/upload', upload.single('bundle'), async (req, res) => { try { const { version, channel, metadata } = req.body; const file = req.file; if (!file) { return res.status(400).json({ error: 'No bundle file provided' }); } // Calculate checksum const fileBuffer = await fs.readFile(file.path); const checksum = crypto.createHash('sha256').update(fileBuffer).digest('hex'); // TODO: Store bundle metadata in database // TODO: Move file to permanent storage res.json({ success: true, bundle: { version, channel, checksum, size: file.size } }); } catch (error) { res.status(500).json({ error: error.message }); } }); export { router }; `; await fs.writeFile(path.join(outputDir, 'routes', 'bundles.js'), bundlesRoute); // Create .env.example const envExample = `# Server Configuration PORT=3000 # Security JWT_SECRET=your-secret-key-here API_KEY=your-api-key-here # Storage STORAGE_PATH=./storage/bundles # Database (optional) # DATABASE_URL=postgresql://user:password@localhost:5432/native_update `; await fs.writeFile(path.join(outputDir, '.env.example'), envExample); // Create README const readme = `# Native Update Backend Express.js backend for Native Update plugin. ## Setup 1. Copy \`.env.example\` to \`.env\` and configure 2. Run \`npm install\` 3. Run \`npm run dev\` for development ## API Endpoints - GET /api/bundles/latest - Get latest bundle for app - POST /api/bundles/upload - Upload new bundle - GET /health - Health check ## Security - Uses JWT for authentication - Rate limiting enabled - CORS configured ${options.withMonitoring ? '## Monitoring\n\nOpenTelemetry monitoring is configured. Set up your preferred backend (Jaeger, Zipkin, etc.)' : ''} `; await fs.writeFile(path.join(outputDir, 'README.md'), readme); } async function createVercelBackend(outputDir, options) { // Create api directory const apiDir = path.join(outputDir, 'api'); await fs.mkdir(apiDir, { recursive: true }); // Create package.json const packageJson = { name: "native-update-vercel", version: "1.0.0", description: "Native Update Vercel backend", type: "module", scripts: { dev: "vercel dev", deploy: "vercel", build: "echo 'No build step required'" }, dependencies: { "@vercel/node": "^3.0.0" } }; await fs.writeFile( path.join(outputDir, 'package.json'), JSON.stringify(packageJson, null, 2) ); // Create bundles API endpoint const bundlesApi = `export default async function handler(req, res) { // Enable CORS res.setHeader('Access-Control-Allow-Credentials', true); res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET,OPTIONS,PATCH,DELETE,POST,PUT'); res.setHeader( 'Access-Control-Allow-Headers', 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' ); if (req.method === 'OPTIONS') { res.status(200).end(); return; } if (req.method === 'GET') { // Get latest bundle const { appId, version, channel = 'production' } = req.query; // TODO: Implement bundle lookup from your storage solution // This is a simplified example const bundle = { version: '1.0.1', url: \`https://\${req.headers.host}/bundles/latest.zip\`, checksum: 'sha256:...', signature: 'signature...', metadata: { releaseNotes: 'Bug fixes and improvements' } }; return res.status(200).json(bundle); } if (req.method === 'POST') { // Handle bundle upload const { version, channel, metadata } = req.body; // TODO: Handle file upload to storage (Vercel Blob, S3, etc.) // TODO: Save metadata to database (Vercel KV, external DB, etc.) return res.status(200).json({ success: true, bundle: { version, channel, message: 'Bundle uploaded successfully' } }); } return res.status(405).json({ error: 'Method not allowed' }); }`; await fs.writeFile(path.join(apiDir, 'bundles.js'), bundlesApi); // Create health check endpoint const healthApi = `export default function handler(req, res) { res.status(200).json({ status: 'ok', service: 'vercel-edge', timestamp: new Date().toISOString() }); }`; await fs.writeFile(path.join(apiDir, 'health.js'), healthApi); // Create vercel.json const vercelConfig = { functions: { "api/*.js": { maxDuration: 30 } }, rewrites: [ { source: "/api/bundles/latest", destination: "/api/bundles" } ] }; await fs.writeFile( path.join(outputDir, 'vercel.json'), JSON.stringify(vercelConfig, null, 2) ); // Create .env.example const envExample = `# Storage Configuration STORAGE_URL=your-storage-url STORAGE_KEY=your-storage-key # Database Configuration (optional) DATABASE_URL=your-database-url # Security API_KEY=your-api-key-here `; await fs.writeFile(path.join(outputDir, '.env.example'), envExample); // Create README const readme = `# Native Update Vercel Backend Serverless backend for Native Update plugin using Vercel Edge Functions. ## Setup 1. Install Vercel CLI: \`npm i -g vercel\` 2. Copy \`.env.example\` to \`.env.local\` and configure 3. Run \`npm install\` 4. Run \`vercel dev\` for local development ## Deployment \`\`\`bash vercel \`\`\` ## API Endpoints - GET /api/bundles - Get latest bundle - POST /api/bundles - Upload new bundle - GET /api/health - Health check ## Storage Options - Vercel Blob Storage - AWS S3 - Cloudflare R2 - Any S3-compatible storage ## Database Options - Vercel KV - Vercel Postgres - PlanetScale - Supabase ${options.withMonitoring ? '## Monitoring\\n\\nUse Vercel Analytics and Logs for monitoring.' : ''} `; await fs.writeFile(path.join(outputDir, 'README.md'), readme); } async function createFirebaseBackend(outputDir, options) { // Create functions directory const functionsDir = path.join(outputDir, 'functions'); await fs.mkdir(functionsDir, { recursive: true }); // Create package.json for functions const packageJson = { name: "native-update-firebase-functions", description: "Firebase Functions backend for Native Update", type: "module", engines: { node: "22" }, main: "index.js", scripts: { serve: "firebase emulators:start --only functions", shell: "firebase functions:shell", start: "npm run serve", deploy: "firebase deploy --only functions", logs: "firebase functions:log" }, dependencies: { "firebase-admin": "^12.0.0", "firebase-functions": "^5.0.0", cors: "^2.8.5", "express": "^5.1.0" } }; await fs.writeFile( path.join(functionsDir, 'package.json'), JSON.stringify(packageJson, null, 2) ); // Create index.js const indexJs = `import { onRequest } from 'firebase-functions/v2/https'; import * as admin from 'firebase-admin'; import express from 'express'; import cors from 'cors'; admin.initializeApp(); const app = express(); app.use(cors({ origin: true })); app.use(express.json()); // Get latest bundle app.get('/api/bundles/latest', async (req, res) => { try { const { appId, version, channel = 'production' } = req.query; // Query Firestore for latest bundle const db = admin.firestore(); const snapshot = await db.collection('bundles') .where('appId', '==', appId) .where('channel', '==', channel) .where('enabled', '==', true) .orderBy('createdAt', 'desc') .limit(1) .get(); if (snapshot.empty) { return res.status(404).json({ error: 'No bundle found' }); } const bundle = snapshot.docs[0].data(); res.json(bundle); } catch (error) { res.status(500).json({ error: error.message }); } }); // Upload bundle app.post('/api/bundles/upload', async (req, res) => { try { const { version, channel, metadata } = req.body; // TODO: Handle file upload to Cloud Storage // TODO: Save metadata to Firestore res.json({ success: true, version }); } catch (error) { res.status(500).json({ error: error.message }); } }); // Health check app.get('/health', (req, res) => { res.json({ status: 'ok', service: 'firebase-functions' }); }); export const api = onRequest(app); `; await fs.writeFile(path.join(functionsDir, 'index.js'), indexJs); // Create firebase.json const firebaseConfig = { functions: { source: "functions", runtime: "nodejs22" }, firestore: { rules: "firestore.rules", indexes: "firestore.indexes.json" }, storage: { rules: "storage.rules" } }; await fs.writeFile( path.join(outputDir, 'firebase.json'), JSON.stringify(firebaseConfig, null, 2) ); // Create Firestore rules const firestoreRules = `rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Bundles are read-only for clients match /bundles/{document=**} { allow read: if true; allow write: if false; } // Analytics can be written by clients match /analytics/{document=**} { allow create: if true; allow read, update, delete: if false; } } }`; await fs.writeFile(path.join(outputDir, 'firestore.rules'), firestoreRules); // Create README const readme = `# Native Update Firebase Backend Firebase Functions backend for Native Update plugin. ## Setup 1. Install Firebase CLI: \`npm install -g firebase-tools\` 2. Login: \`firebase login\` 3. Initialize: \`firebase init\` 4. Select your project or create new 5. Install dependencies: \`cd functions && npm install\` 6. Start emulator: \`npm run serve\` ## Deployment \`\`\`bash firebase deploy \`\`\` ## Endpoints - GET /api/bundles/latest - Get latest bundle - POST /api/bundles/upload - Upload new bundle - GET /health - Health check ${options.withMonitoring ? '## Monitoring\n\nUse Firebase Console for monitoring and analytics.' : ''} `; await fs.writeFile(path.join(outputDir, 'README.md'), readme); }