stremio-community-v5/build/deploy_windows.js
2025-08-20 22:42:25 +02:00

302 lines
No EOL
11 KiB
JavaScript

#!/usr/bin/env node
/****************************************************************************
* deploy_windows.js
*
* Builds windows distributable folder dist/win.
* Pass --installer to also build the windows installer
* Make sure to have set up utils/windows and the environment correctly by following windows.md
*
****************************************************************************/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
// ---------------------------------------------------------------------
// Project/Layout Configuration
// ---------------------------------------------------------------------
const ARCH = process.argv.includes('--x86') ? 'x86' : 'x64';
const SOURCE_DIR = path.resolve(__dirname, '..');
const BUILD_DIR = path.join(SOURCE_DIR, `cmake-build-release-${ARCH}`);
const DIST_DIR = path.join(SOURCE_DIR, 'dist', `win-${ARCH}`);
const CONFIG_DIR = path.join(SOURCE_DIR, 'dist', `win-${ARCH}`, 'portable_config');
const PROJECT_NAME = 'stremio';
// Paths to Additional Dependencies
const MPV_DLL = ARCH === 'x86'
? path.join(SOURCE_DIR, 'deps', 'libmpv', 'i686', 'libmpv-2.dll')
: path.join(SOURCE_DIR, 'deps', 'libmpv', 'x86_64', 'libmpv-2.dll');
const SERVER_JS = path.join(SOURCE_DIR, 'utils', 'windows', 'server.js');
const STREMIO_RUNTIME_EXE = path.join(SOURCE_DIR, 'utils', 'windows', 'stremio-runtime.exe');
const FFMPEG_FOLDER = path.join(SOURCE_DIR, 'utils', 'windows', 'ffmpeg');
const MPV_FOLDER = path.join(SOURCE_DIR, 'utils', 'mpv', 'anime4k');
const DEFAULT_SETTINGS_FOLDER = path.join(SOURCE_DIR, 'utils', 'stremio');
// Default Paths
const DEFAULT_NSIS = 'C:\\Program Files (x86)\\NSIS\\makensis.exe';
//VCPKG
const VCPKG_TRIPLET = ARCH === 'x86' ? 'x86-windows-static' : 'x64-windows-static';
const VCPKG_CMAKE = 'G:\\Documents\\Github\\vcpkg\\scripts\\buildsystems\\vcpkg.cmake';
// ---------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------
(async function main() {
try {
console.log(`\n=== Building for ${ARCH.toUpperCase()} ===`);
const args = process.argv.slice(2);
const buildInstaller = args.includes('--installer');
const buildPortable = args.includes('--portable');
const debugBuild = args.includes('--debug');
// 3) Run CMake + Ninja in ../cmake-build-release (64-bit)
if (!fs.existsSync(BUILD_DIR)) {
fs.mkdirSync(BUILD_DIR, { recursive: true });
}
console.log(`\n=== Running CMake in cmake-build-${debugBuild ? "Debug" : "Release"} ===`);
process.chdir(BUILD_DIR);
execSync(
`cmake -G Ninja -DCMAKE_BUILD_TYPE=${debugBuild ? "Debug" : "Release"} -DCMAKE_TOOLCHAIN_FILE=${VCPKG_CMAKE} -DVCPKG_TARGET_TRIPLET=${VCPKG_TRIPLET} ..`,
{ stdio: 'inherit' }
);
console.log('=== Running Ninja in cmake-build-release ===');
execSync('ninja', { stdio: 'inherit' });
// Return to script directory
process.chdir(__dirname);
// 4) Prepare dist\win
console.log(`\n=== Cleaning and creating ${DIST_DIR} ===`);
safeRemove(DIST_DIR);
fs.mkdirSync(DIST_DIR, { recursive: true });
// 5) Copy main .exe
const builtExe = path.join(BUILD_DIR, `${PROJECT_NAME}.exe`);
const distExe = path.join(DIST_DIR, `${PROJECT_NAME}.exe`);
copyFile(builtExe, distExe);
// 6) Copy mpv DLL, server.js, node.exe
copyFile(MPV_DLL, path.join(DIST_DIR, path.basename(MPV_DLL)));
copyFile(SERVER_JS, path.join(DIST_DIR, path.basename(SERVER_JS)));
// 8) Flatten stremio-runtime, ffmpeg
console.log('Flattening DS folder, stremio-runtime, ffmpeg...');
copyFile(STREMIO_RUNTIME_EXE, path.join(DIST_DIR, 'stremio-runtime.exe'));
copyFolderContents(FFMPEG_FOLDER, DIST_DIR);
copyFolderContentsPreservingStructure(MPV_FOLDER, DIST_DIR);
copyFolderContentsPreservingStructure(DEFAULT_SETTINGS_FOLDER, CONFIG_DIR);
console.log('\n=== dist\\win preparation complete. ===');
// 10) If --installer, parse version and build NSIS
if (buildInstaller) {
console.log('\n--installer detected: building NSIS installer...');
// Extract the version first so we can set process.env before calling NSIS
const version = getPackageVersionFromCMake();
process.env.package_version = version;
console.log(`Set package_version to: ${version}`);
buildNsisInstaller();
} else if (buildPortable) {
console.log('\n--portable detected: building Portable...');
buildPortableZip();
}
console.log('\nAll done!');
} catch (err) {
console.error('Error in deploy_windows.js:', err);
process.exit(1);
}
})();
/****************************************************************************
* Helper Functions
****************************************************************************/
function safeRemove(dirPath) {
if (fs.existsSync(dirPath)) {
fs.rmSync(dirPath, { recursive: true, force: true });
}
}
function copyFile(src, dest) {
if (!fs.existsSync(src)) {
console.warn(`Warning: missing file: ${src}`);
return;
}
fs.copyFileSync(src, dest);
console.log(`Copied: ${src} -> ${dest}`);
}
/**
* Recursively copies only the contents of "src" into "dest" (flattened).
* If src has files/folders, they go directly into dest, rather than
* creating a subfolder named src.
*/
function copyFolderContents(src, dest) {
if (!fs.existsSync(src)) {
console.warn(`Warning: missing folder: ${src}`);
return;
}
const stats = fs.statSync(src);
if (!stats.isDirectory()) {
console.warn(`Warning: not a directory: ${src}`);
return;
}
for (const item of fs.readdirSync(src)) {
const srcItem = path.join(src, item);
const itemStats = fs.statSync(srcItem);
const destItem = path.join(dest, item);
if (itemStats.isDirectory()) {
copyFolderContents(srcItem, dest);
} else {
copyFile(srcItem, destItem);
}
}
}
/**
* Copies the contents of `src` into `dest` without flattening.
* Subdirectories in `src` will be recreated in `dest`.
*/
function copyFolderContentsPreservingStructure(src, dest) {
if (!fs.existsSync(src)) {
console.warn(`Warning: missing folder: ${src}`);
return;
}
const stats = fs.statSync(src);
if (!stats.isDirectory()) {
console.warn(`Warning: not a directory: ${src}`);
return;
}
// Ensure destination directory exists
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const items = fs.readdirSync(src);
for (const item of items) {
const srcItem = path.join(src, item);
const destItem = path.join(dest, item);
const itemStats = fs.statSync(srcItem);
if (itemStats.isDirectory()) {
// Recursively copy subdirectories
copyFolderContentsPreservingStructure(srcItem, destItem);
} else {
if (!srcItem.endsWith('zip') && !srcItem.endsWith('7z')) {
// Copy files
copyFile(srcItem, destItem);
}
}
}
}
/**
* Retrieves version from CMakeLists.txt (handles quotes):
* project(stremio VERSION "5.0.2")
*/
function getPackageVersionFromCMake() {
const cmakeFile = path.join(SOURCE_DIR, 'CMakeLists.txt');
let version = '0.0.0';
if (fs.existsSync(cmakeFile)) {
const content = fs.readFileSync(cmakeFile, 'utf8');
// Accept either quoted or unquoted numerical version
const match = content.match(/project\s*\(\s*stremio\s+VERSION\s+"?([\d.]+)"?\)/i);
if (match) {
version = match[1];
}
}
return version;
}
function buildNsisInstaller() {
if (!fs.existsSync(DEFAULT_NSIS)) {
console.warn(`NSIS not found at default path: ${DEFAULT_NSIS}. Skipping installer.`);
return;
}
try {
const arch = process.argv.includes('--x86') ? 'x86' : 'x64'; // Determine architecture
const distSubfolder = `win-${arch}`;
const distFolder = path.join(SOURCE_DIR, 'dist', distSubfolder);
if (!fs.existsSync(distFolder)) {
console.error(`Error: Distribution folder does not exist: ${distFolder}`);
process.exit(1);
}
const nsiScript = path.join(SOURCE_DIR, 'utils', 'windows', 'installer', 'windows-installer.nsi');
console.log(`Running makensis.exe with version: ${process.env.package_version} ...`);
process.env.arch = arch;
execSync(`"${DEFAULT_NSIS}" "${nsiScript}"`, { stdio: 'inherit' });
console.log(`\nInstaller created: "Stremio ${process.env.package_version}.exe"`);
} catch (err) {
console.error('Failed to run NSIS (makensis.exe):', err);
}
}
function buildPortableZip() {
const version = getPackageVersionFromCMake();
const portableOutput = path.join(SOURCE_DIR, 'utils', `Stremio ${version}-${ARCH}.7z`);
const fixedEdgeWebView = path.join(SOURCE_DIR, 'utils', 'windows', 'WebviewRuntime', ARCH);
const portable_config = path.join(DIST_DIR, 'portable_config');
const distContents = DIST_DIR; // Path to dist directory contents
console.log(`\nCreating Portable ZIP: ${portableOutput}`);
// Common 7-Zip paths
const common7zPaths = [
'C:\\Program Files\\7-Zip\\7z.exe',
'C:\\Program Files (x86)\\7-Zip\\7z.exe'
];
// Find 7-Zip executable
const sevenZipPath = common7zPaths.find(fs.existsSync);
if (!sevenZipPath) {
console.error('Error: 7-Zip executable not found in common paths.');
console.error('Please install 7-Zip and ensure it is in one of the following paths:');
console.error(common7zPaths.join('\n'));
process.exit(1);
}
console.log(`Using 7-Zip at: ${sevenZipPath}`);
// Ensure the DIST_DIR exists
if (!fs.existsSync(DIST_DIR)) {
console.error(`Error: DIST_DIR does not exist: ${DIST_DIR}`);
process.exit(1);
}
copyFolderContentsPreservingStructure(fixedEdgeWebView, portable_config);
// Command to create the 7z archive
const zipCommand = `"${sevenZipPath}" a -t7z -mx=9 "${portableOutput}" "${distContents}\\*"`;
try {
// Run the 7-Zip command
console.log(`Running: ${zipCommand}`);
execSync(zipCommand, { stdio: 'inherit' });
console.log(`\nPortable ZIP created: ${portableOutput}`);
//Clean UP
const portableConfigWebView = path.join(DIST_DIR, 'portable_config', 'EdgeWebView');
if (fs.existsSync(portableConfigWebView)) {
console.log(`\nCleaning up: ${portableConfigWebView}`);
fs.rmSync(portableConfigWebView, { recursive: true, force: true });
console.log(`Removed: ${portableConfigWebView}`);
} else {
console.log(`\nNo cleanup needed: ${portableConfigWebView} does not exist.`);
}
} catch (error) {
console.error('Error creating the Portable ZIP:', error);
process.exit(1);
}
}