diff --git a/package.json b/package.json index a09c473c..8fdae7ca 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ "localization": "node ./src/localization/localizationHelperCLI.js", "prod": "cross-env PLATFORM=windows webpack --config webpack.config.js --mode production", "prod-linux": "cross-env PLATFORM=linux webpack --config webpack.config.js --mode production", - "build-electron": "node ./src-electron/patch-package-version.js && electron-builder --publish never", + "build-electron": "node ./src-electron/download-dotnet-runtime.js && node ./src-electron/patch-package-version.js && electron-builder --publish never", "postbuild-electron": "node ./src-electron/patch-node-api-dotnet.js && node ./src-electron/rename-builds.js", "start-electron": "electron ." }, @@ -91,7 +91,8 @@ "node_modules/node-api-dotnet/**/*", "node_modules/node-api-dotnet/net9.0/**/*", "build/Electron/*", - "build/Electron/**" + "build/Electron/**", + "build/Electron/dotnet-runtime/**/*" ], "extraResources": [ { @@ -109,6 +110,10 @@ { "from": "node_modules/node-api-dotnet/net9.0/Microsoft.JavaScript.NodeApi.DotNetHost.dll", "to": "app.asar.unpacked/node_modules/node-api-dotnet/net9.0/Microsoft.JavaScript.NodeApi.DotNetHost.dll" + }, + { + "from": "build/Electron/dotnet-runtime/", + "to": "dotnet-runtime/" } ], "directories": { @@ -140,4 +145,4 @@ "hazardous": "^0.3.0", "node-api-dotnet": "^0.9.12" } -} +} \ No newline at end of file diff --git a/src-electron/download-dotnet-runtime.js b/src-electron/download-dotnet-runtime.js new file mode 100644 index 00000000..cd238a27 --- /dev/null +++ b/src-electron/download-dotnet-runtime.js @@ -0,0 +1,119 @@ +const fs = require('fs'); +const path = require('path'); +const https = require('https'); +const { spawnSync } = require('child_process'); + +const DOTNET_VERSION = '9.0.7'; +const DOTNET_RUNTIME_URL = `https://builds.dotnet.microsoft.com/dotnet/Runtime/${DOTNET_VERSION}/dotnet-runtime-${DOTNET_VERSION}-linux-x64.tar.gz`; +const DOTNET_RUNTIME_DIR = path.join(__dirname, '..', 'build', 'Electron', 'dotnet-runtime'); +const DOTNET_BIN_DIR = path.join(DOTNET_RUNTIME_DIR, 'bin'); + +async function downloadFile(url, targetPath) { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(targetPath); + https.get(url, (response) => { + if (response.statusCode !== 200) { + reject(new Error(`Failed to download, status code: ${response.statusCode}`)); + return; + } + response.pipe(file); + file.on('finish', () => { + file.close(resolve); + }); + }).on('error', (err) => { + fs.unlink(targetPath, () => reject(err)); + }); + }); +} + +async function extractTarGz(tarGzPath, extractDir) { + return new Promise((resolve, reject) => { + const tar = spawnSync('tar', ['-xzf', tarGzPath, '-C', extractDir, '--strip-components=1'], { + stdio: 'inherit' + }); + + if (tar.status === 0) { + resolve(); + } else { + reject(new Error(`tar extraction failed with status ${tar.status}`)); + } + }); +} + +async function main() { + console.log(`Downloading .NET ${DOTNET_VERSION} runtime...`); + + if (!fs.existsSync(DOTNET_RUNTIME_DIR)) { + fs.mkdirSync(DOTNET_RUNTIME_DIR, { recursive: true }); + } + if (!fs.existsSync(DOTNET_BIN_DIR)) { + fs.mkdirSync(DOTNET_BIN_DIR, { recursive: true }); + } + + const tarGzPath = path.join(DOTNET_RUNTIME_DIR, 'dotnet-runtime.tar.gz'); + + try { + // Download .NET runtime + await downloadFile(DOTNET_RUNTIME_URL, tarGzPath); + console.log('Download completed'); + + // Extract .NET runtime to a temporary directory first + const tempExtractDir = path.join(DOTNET_RUNTIME_DIR, 'temp'); + if (!fs.existsSync(tempExtractDir)) { + fs.mkdirSync(tempExtractDir, { recursive: true }); + } + + console.log('Extracting .NET runtime...'); + await extractTarGz(tarGzPath, tempExtractDir); + console.log('Extraction completed'); + + // Clean up tar.gz file + fs.unlinkSync(tarGzPath); + console.log('Cleanup completed'); + + // Move dotnet executable to bin directory + const extractedDotnet = path.join(tempExtractDir, 'dotnet'); + const targetDotnet = path.join(DOTNET_BIN_DIR, 'dotnet'); + + if (fs.existsSync(extractedDotnet)) { + fs.renameSync(extractedDotnet, targetDotnet); + fs.chmodSync(targetDotnet, 0o755); + console.log('Moved dotnet executable to bin directory'); + } + + // Move all other files to the root of dotnet-runtime + const files = fs.readdirSync(tempExtractDir); + for (const file of files) { + const sourcePath = path.join(tempExtractDir, file); + const targetPath = path.join(DOTNET_RUNTIME_DIR, file); + + if (fs.existsSync(targetPath)) { + if (fs.lstatSync(sourcePath).isDirectory()) { + // Remove existing directory and move new one + fs.rmSync(targetPath, { recursive: true, force: true }); + } else { + // Remove existing file + fs.unlinkSync(targetPath); + } + } + + fs.renameSync(sourcePath, targetPath); + } + + // Clean up temp directory + fs.rmSync(tempExtractDir, { recursive: true, force: true }); + + console.log(`.NET runtime downloaded and extracted to: ${DOTNET_RUNTIME_DIR}`); + console.log(`dotnet executable available at: ${targetDotnet}`); + + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +} + +if (require.main === module) { + main(); +} + +module.exports = { downloadFile, extractTarGz }; \ No newline at end of file diff --git a/src-electron/main.js b/src-electron/main.js index 1598876e..7e8e073c 100644 --- a/src-electron/main.js +++ b/src-electron/main.js @@ -13,6 +13,15 @@ const { spawn, spawnSync } = require('child_process'); const fs = require('fs'); const https = require('https'); +// Include bundled .NET runtime +const bundledDotNetPath = path.join(process.resourcesPath, 'dotnet-runtime'); +const bundledDotnet = path.join(bundledDotNetPath, 'bin', 'dotnet'); + +if (fs.existsSync(bundledDotnet)) { + process.env.DOTNET_ROOT = bundledDotNetPath; + process.env.PATH = `${path.dirname(bundledDotnet)}:${process.env.PATH}`; +} + if (!isDotNetInstalled()) { app.whenReady().then(() => { dialog.showErrorBox( @@ -24,7 +33,7 @@ if (!isDotNetInstalled()) { return; } -// get launch arguments +// Get launch arguments let appImagePath = process.env.APPIMAGE; const args = process.argv.slice(1); const noInstall = args.includes('--no-install'); @@ -527,6 +536,14 @@ function isDotNetInstalled() { // Assume .NET is already installed on macOS return true; } + + // Check for bundled .NET runtime + if (fs.existsSync(bundledDotnet)) { + console.log('Using bundled .NET runtime at:', bundledDotNetPath); + return true; + } + + // Fallback to system .NET runtime const result = spawnSync('dotnet', ['--list-runtimes'], { encoding: 'utf-8' });