/* * Copyright (C) 2026 TomTom Navigation B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Build script for MCP Apps - parallel builds with vite-plugin-singlefile * * Concurrency is capped to avoid OOM when building all apps at once. */ import path from 'path'; import fs from 'fs'; import { build } from 'vite'; import { APPS_DIR, ROOT_DIR, appViteConfig } from './appViteConfig'; const ALL_CATEGORIES = ['traffic', 'map', 'data-viz']; const DIST_DIR = path.join(ROOT_DIR, 'dist/apps'); const filterCategory = process.env.CATEGORY; const APP_CATEGORIES = filterCategory ? [filterCategory] : ALL_CATEGORIES; interface AppEntry { category: string; name: string; id: string; htmlPath: string; appDir: string; } function discoverApps(): AppEntry[] { const apps: AppEntry[] = []; for (const category of APP_CATEGORIES) { const categoryDir = path.join(APPS_DIR, category); if (!fs.existsSync(categoryDir)) continue; for (const appName of fs.readdirSync(categoryDir)) { const appDir = path.join(categoryDir, appName); if (!fs.statSync(appDir).isDirectory()) continue; const htmlPath = path.join(appDir, 'app.html'); if (!fs.existsSync(htmlPath)) continue; apps.push({ category, name: appName, id: `${category}/${appName}`, htmlPath, appDir }); } } return apps; } async function buildApp(app: AppEntry): Promise<{ app: AppEntry; success: boolean; error?: string }> { try { await build( appViteConfig({ appDir: app.appDir, htmlPath: app.htmlPath, outDir: path.join(DIST_DIR, app.id), }) ); return { app, success: true }; } catch (e: unknown) { return { app, success: false, error: e instanceof Error ? e.message : String(e) }; } } async function runBatch(apps: AppEntry[], concurrency: number) { const results = []; for (let i = 0; i < apps.length; i += concurrency) { const batch = apps.slice(i, i + concurrency); results.push(...(await Promise.all(batch.map(buildApp)))); } return results; } const apps = discoverApps(); if (apps.length === 0) { console.log('No apps found to build.'); process.exit(0); } console.log('Building MCP Apps...\n'); const start = Date.now(); const results = await runBatch(apps, 4); let success = 0, failed = 0; for (const r of results) { if (r.success) { console.log(`✅ ${r.app.id}`); success++; } else { console.error(`❌ ${r.app.id}: ${r.error}`); failed++; } } console.log(`\nBuilt ${success} apps in ${((Date.now() - start) / 1000).toFixed(1)}s`); if (failed > 0) process.exit(1);