#!/usr/bin/env bash # Build a standalone Samsung Tizen TV app (.wgt) from Retlix. # Usage: ./build-tizen.sh [SERVER_IP] set -euo pipefail cd "$(dirname "$0")" IP="${1:-$(ipconfig getifaddr en0 2>/dev/null || hostname -I 2>/dev/null | awk '{print $1}')}" echo "📺 Building Retlix Tizen app (backend: http://$IP:3000)" # 1) Build frontend with relative paths node -e " const fs = require('fs'); let c = fs.readFileSync('vite.config.js','utf8'); if (!c.includes(\"base: './'\")) { c = c.replace('build: {', \"base: './',\\n build: {\"); fs.writeFileSync('vite.config.js', c); }" npm run build 2>&1 | tail -3 node -e " const fs = require('fs'); let c = fs.readFileSync('vite.config.js','utf8'); c = c.replace(\"base: './',\\n \", ''); fs.writeFileSync('vite.config.js', c);" # 2) Prepare tizen-build OUT="tizen-build" rm -rf "$OUT" mkdir -p "$OUT/assets" cp dist/assets/* "$OUT/assets/" cp tizen/config.xml "$OUT/" cp tizen/icon.png "$OUT/" cp tizen/banner.png "$OUT/" # 3) Post-process CSS: inline variables + fix gap for Chrome 47 CSS_SRC=$(ls dist/assets/index-*.css) CSS_NAME=$(basename "$CSS_SRC") node -e " const fs = require('fs'); let css = fs.readFileSync('$CSS_SRC', 'utf8'); const vars = { '--red': '#e50914', '--red-hover': '#f6121d', '--bg': '#141414', '--bg-2': '#181818', '--text': '#fff', '--muted': '#b3b3b3', '--card-radius': '6px', '--nav-h': '68px' }; for (const [k, v] of Object.entries(vars)) { const esc = k.replace(/[-]/g, '\\\\-'); css = css.replace(new RegExp('var\\\\(' + esc + '\\\\)', 'g'), v); } css = css.replace(/:root\{[^}]*\}/, ''); // Remove @supports blocks — Chrome 47 doesn't support them properly css = css.replace(/@supports\s*\([^)]*\)\s*\{[^}]*\}/g, ''); // Remove flattened overrides that undo the padding-top fallback css = css.replace(/\.card\{height:auto;padding-top:0\}/g, ''); css = css.replace(/\.card\.poster\{padding-top:0\}/g, ''); css = css.replace(/\.modal-hero\{height:auto;padding-top:0\}/g, ''); // Replace min(Xpx, Y%) with Y%;max-width:Xpx (Chrome 47 doesn't support min()) css = css.replace(/width:\s*min\((\d+px),\s*(\d+%?)\)/g, 'width:\$2;max-width:\$1'); // Replace clamp(min, preferred, max) with just the preferred value as fallback css = css.replace(/font-size:\s*clamp\([^,]+,\s*([^,]+),\s*[^)]+\)/g, 'font-size:\$1'); // Card children need absolute positioning inside the padding-top box (non-TV fallback) css += '\\n.card img,.card-fallback,.card-playhint,.card-hovercap{position:absolute;top:0;left:0;width:100%;height:100%}'; css += '\\n.nav-links>*+*{margin-left:20px}.nav-right>*+*{margin-left:18px}.hero-actions>*+*{margin-left:12px}.hero-meta>*+*{margin-left:12px}.pl-controls>*+*{margin-left:14px}.pl-center>*+*{margin-left:34px}.modal-meta>*+*{margin-left:14px}.chips>*+*{margin-left:8px}.toolbar>*+*{margin-left:10px}.loader>*+*{margin-left:14px}.page-head>*+*{margin-left:16px}.sync-counts>*+*{margin-left:26px}.pl-vol>*+*{margin-left:4px}.pl-popover>*+*{margin-left:30px}.search-bar>*+*{margin-left:12px}.pl-ep-head>*+*{margin-left:14px}.kbd-actions>*+*{margin-left:8px}.tv-toggle>*+*{margin-left:12px}.row-track>*+*{margin-left:8px}.cast-track>*+*{margin-left:14px}.nav>*+*{margin-left:26px}'; fs.writeFileSync('$OUT/assets/$CSS_NAME', css); console.log('CSS: variables inlined + gap polyfilled'); " # 4) Get legacy JS filenames POLYFILL=$(basename "$(ls dist/assets/polyfills-legacy-*.js)") LEGACY=$(basename "$(ls dist/assets/index-legacy-*.js)") # 5) Create standalone Tizen index.html cat > "$OUT/index.html" << HTMLEOF