From 6da483d3c6e4edbba4259b7e965f3ce88dc60281 Mon Sep 17 00:00:00 2001 From: opariffazman Date: Tue, 16 Dec 2025 20:39:22 +0800 Subject: [PATCH 1/5] feat: add platform detection utility for Termux support --- src/utils/platform-detector.ts | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 src/utils/platform-detector.ts diff --git a/src/utils/platform-detector.ts b/src/utils/platform-detector.ts new file mode 100644 index 0000000..6887299 --- /dev/null +++ b/src/utils/platform-detector.ts @@ -0,0 +1,88 @@ +import { existsSync } from 'fs'; + +export interface PlatformConfig { + isTermux: boolean; + chromiumPath: string | null; + browserPackage: 'playwright' | 'playwright-core'; + requiresSystemChromium: boolean; +} + +/** + * Detect if running on Termux and return platform-specific configuration + */ +export function detectPlatform(): PlatformConfig { + // Allow manual override via environment variable + if (process.env.FORCE_PLAYWRIGHT_CORE === 'true') { + return { + isTermux: true, + chromiumPath: process.env.CHROMIUM_PATH || '/data/data/com.termux/files/usr/bin/chromium-browser', + browserPackage: 'playwright-core', + requiresSystemChromium: true + }; + } + + // Detection logic (in priority order): + // 1. Check for CHROMIUM_PATH environment variable (explicit Termux setup) + const chromiumPath = process.env.CHROMIUM_PATH; + + // 2. Check for PREFIX environment variable (Termux sets this) + const hasTermuxPrefix = process.env.PREFIX === '/data/data/com.termux/files/usr'; + + // 3. Check for Android platform + const isAndroid = process.platform === 'android'; + + // 4. Check if Termux chromium binary exists + const defaultTermuxChromiumPath = '/data/data/com.termux/files/usr/bin/chromium-browser'; + const hasTermuxChromium = existsSync(defaultTermuxChromiumPath); + + const isTermux = Boolean( + chromiumPath || + hasTermuxPrefix || + (isAndroid && hasTermuxChromium) + ); + + return { + isTermux, + chromiumPath: isTermux + ? (chromiumPath || defaultTermuxChromiumPath) + : null, + browserPackage: isTermux ? 'playwright-core' : 'playwright', + requiresSystemChromium: isTermux + }; +} + +/** + * Get the chromium executable path for the current platform + * Returns undefined for non-Termux platforms (uses playwright's bundled chromium) + */ +export function getChromiumExecutablePath(): string | undefined { + const config = detectPlatform(); + return config.chromiumPath || undefined; +} + +/** + * Validate Termux setup and return helpful error message if invalid + */ +export function validateTermuxSetup(): { valid: boolean; error?: string } { + const config = detectPlatform(); + + if (!config.isTermux) { + return { valid: true }; + } + + if (!config.chromiumPath) { + return { + valid: false, + error: 'CHROMIUM_PATH not set.\n\nRun: export CHROMIUM_PATH=/data/data/com.termux/files/usr/bin/chromium-browser' + }; + } + + if (!existsSync(config.chromiumPath)) { + return { + valid: false, + error: `Chromium not found at: ${config.chromiumPath}\n\nInstall with: pkg install chromium` + }; + } + + return { valid: true }; +} From 5acf95c14c629999458ff6824854bf5deea1701c Mon Sep 17 00:00:00 2001 From: opariffazman Date: Tue, 16 Dec 2025 20:39:30 +0800 Subject: [PATCH 2/5] feat: add playwright-core dependency and Termux-specific scripts --- package.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 4852d1f..85e2a17 100644 --- a/package.json +++ b/package.json @@ -18,12 +18,16 @@ }, "scripts": { "dev": "bun run src/index.tsx", + "dev:termux": "tsx src/index.tsx", "build": "bun build src/index.tsx --outdir dist --target node --minify --external playwright", + "build:termux": "esbuild src/index.tsx --bundle --platform=node --target=node18 --outdir=dist --minify --packages=external --format=esm", "postbuild": "node -e \"const fs = require('fs'); fs.cpSync('src/templates', 'dist/templates', {recursive: true}); fs.cpSync('node_modules/yoga-wasm-web/dist/yoga.wasm', 'dist/yoga.wasm');\"", "build:types": "tsc --emitDeclarationOnly", "start": "node dist/index.js", "prepublishOnly": "bun run build && bun run build:types", - "test": "bun test" + "prepublishOnly:termux": "npm run build:termux && npm run build:types", + "test": "bun test", + "test:termux": "vitest" }, "keywords": [ "github", @@ -50,14 +54,17 @@ "ink-spinner": "^5.0.0", "ink-text-input": "^5.0.1", "playwright": "^1.57.0", + "playwright-core": "^1.57.0", "react": "^18.2.0" }, "devDependencies": { "@types/node": "^20.10.5", "@types/react": "^18.2.45", + "esbuild": "^0.24.2", "react-devtools-core": "7.0.1", "tsx": "^4.7.0", - "typescript": "^5.3.3" + "typescript": "^5.3.3", + "vitest": "^2.1.8" }, "engines": { "node": ">=18.0.0", From 85fec1efe95981e42df465e6c80697ae09f22151 Mon Sep 17 00:00:00 2001 From: opariffazman Date: Tue, 16 Dec 2025 20:39:39 +0800 Subject: [PATCH 3/5] feat: implement dynamic playwright loading with Termux executablePath support --- src/export-playwright.ts | 50 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/src/export-playwright.ts b/src/export-playwright.ts index cc33a91..6c92d44 100644 --- a/src/export-playwright.ts +++ b/src/export-playwright.ts @@ -1,4 +1,4 @@ -import { chromium, Browser } from 'playwright'; +import type { Browser, BrowserType } from 'playwright-core'; import { promises as fs, readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; @@ -7,6 +7,22 @@ import { calculateScore, determineTier, getTierName, type Tier } from './tier-ca import { fetchAvatarAsBase64 } from './utils/avatar-fetcher.js'; import { injectDataIntoTemplate } from './utils/html-injector.js'; import { getBrowserInstaller } from './utils/browser-installer.js'; +import { detectPlatform } from './utils/platform-detector.js'; + +/** + * Dynamically load chromium from the appropriate package based on platform + */ +async function getChromium(): Promise { + const platform = detectPlatform(); + + if (platform.isTermux) { + const { chromium } = await import('playwright-core'); + return chromium; + } else { + const { chromium } = await import('playwright'); + return chromium; + } +} const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -75,10 +91,23 @@ export class PlaywrightExporter { await this.closeBrowser(); if (error instanceof Error && error.message?.includes('browserType.launch')) { + const platform = detectPlatform(); + + if (platform.isTermux) { + throw new Error( + 'Failed to launch Chromium on Termux!\n\n' + + 'Steps:\n' + + '1. Install: pkg install chromium\n' + + '2. Set: export CHROMIUM_PATH=/data/data/com.termux/files/usr/bin/chromium-browser\n' + + '3. Retry with [E]\n\n' + + 'Path checked: ' + (platform.chromiumPath || 'not set') + ); + } + throw new Error( 'Chromium browser not installed!\n\n' + - 'Install with: bunx playwright install chromium\n' + - 'Then retry export with [R]' + 'Install with: npx playwright install chromium\n' + + 'Then retry export with [E]' ); } @@ -119,7 +148,11 @@ export class PlaywrightExporter { */ private async renderHTMLToPNG(htmlContent: string, onProgress?: (status: string) => void): Promise { onProgress?.('Starting browser...'); - this.browser = await chromium.launch({ + + const chromium = await getChromium(); + const platform = detectPlatform(); + + const launchOptions: any = { headless: true, args: [ '--disable-dev-shm-usage', @@ -129,7 +162,14 @@ export class PlaywrightExporter { '--force-color-profile=srgb' ], timeout: 60000 - }); + }; + + // Add executablePath for Termux + if (platform.isTermux && platform.chromiumPath) { + launchOptions.executablePath = platform.chromiumPath; + } + + this.browser = await chromium.launch(launchOptions); const page = await this.browser.newPage(); await page.setViewportSize({ width: 440, height: 680 }); From d9a8aed40c5dc39d54dc785735612914e43211dc Mon Sep 17 00:00:00 2001 From: opariffazman Date: Tue, 16 Dec 2025 20:39:48 +0800 Subject: [PATCH 4/5] feat: add Termux detection to browser installer with manual setup instructions --- src/utils/browser-installer.ts | 35 ++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/utils/browser-installer.ts b/src/utils/browser-installer.ts index 5915a10..8e545e9 100644 --- a/src/utils/browser-installer.ts +++ b/src/utils/browser-installer.ts @@ -1,6 +1,7 @@ import { exec } from 'child_process'; import { existsSync } from 'fs'; import { promisify } from 'util'; +import { detectPlatform } from './platform-detector.js'; const execAsync = promisify(exec); @@ -15,9 +16,19 @@ export class BackgroundBrowserInstaller { /** * Check if Chromium is installed */ - private isChromiumInstalled(): boolean { + private async isChromiumInstalled(): Promise { + const platform = detectPlatform(); + + if (platform.isTermux) { + // Check system chromium for Termux + return platform.chromiumPath + ? existsSync(platform.chromiumPath) + : false; + } + + // Check playwright chromium for regular environments try { - const { chromium } = require('playwright'); + const { chromium } = await import('playwright'); const executablePath = chromium.executablePath(); return existsSync(executablePath); } catch { @@ -40,13 +51,29 @@ export class BackgroundBrowserInstaller { private async checkAndInstall(): Promise { try { this.status = 'checking'; + const platform = detectPlatform(); - if (this.isChromiumInstalled()) { + if (await this.isChromiumInstalled()) { this.status = 'ready'; return; } - // Not installed - start installation + // Termux requires manual installation + if (platform.isTermux) { + this.status = 'error'; + this.error = new Error( + 'Termux requires system Chromium.\n\n' + + 'Install with:\n' + + ' pkg install chromium\n\n' + + 'Set environment:\n' + + ' export CHROMIUM_PATH=/data/data/com.termux/files/usr/bin/chromium-browser\n\n' + + 'Add to ~/.bashrc for persistence' + ); + this.progress = 'Manual installation required'; + return; + } + + // Regular environment - auto-install this.status = 'installing'; this.progress = 'Installing Chromium (one-time, ~200MB)...'; From 263931df79df0f58b310f0307947bcbef6a24220 Mon Sep 17 00:00:00 2001 From: opariffazman Date: Tue, 16 Dec 2025 20:39:57 +0800 Subject: [PATCH 5/5] docs: add Termux setup instructions and usage guide --- README.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/README.md b/README.md index 63a0abb..712645a 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,43 @@ node dist/index.js **⚠️ Important:** Always use **Node** to run the built version (`npm start` or `node dist/index.js`), not `bun start`. The image export feature uses Playwright which has known compatibility issues with Bun's runtime, especially on Windows. Bun is great for development and building, but Node is required for running the final output. +### For Termux (Android) + +Running on Android via Termux requires special setup: + +**Prerequisites:** +```bash +# Install required packages +pkg install nodejs chromium git +``` + +**Setup:** +```bash +# Clone the repository +git clone https://github.com/d3varaja/gh-wrapped-cli.git +cd gh-wrapped-cli + +# Install dependencies +npm install --legacy-peer-deps + +# Set environment variables (add to ~/.bashrc for persistence) +export CHROMIUM_PATH=/data/data/com.termux/files/usr/bin/chromium-browser +export PLAYWRIGHT_BROWSERS_PATH=0 + +# Build for Termux +npm run build:termux + +# Run the app +npm start +``` + +**Available Termux-specific scripts:** +- `npm run dev:termux` - Run in development mode +- `npm run build:termux` - Build for Termux (uses esbuild) +- `npm run test:termux` - Run tests (uses vitest) + +**Note:** The app automatically detects Termux and uses system Chromium instead of downloading Playwright's bundled browser, saving ~200MB of storage. + ## How It Works 1. Fetches your public GitHub data via GitHub API