From 2ee5f73117f8812282be49571f6d0dda89a3f633 Mon Sep 17 00:00:00 2001 From: Michel Roegl-Brunner Date: Fri, 7 Nov 2025 12:36:43 +0100 Subject: [PATCH] Fix Install button not appearing for VM and tools scripts - Update checkScriptExists to preserve subdirectory structure for tools and VM scripts - Set ctExists=true for tools and VM scripts when files exist (not just CT scripts) - Add missing compareScriptContent method to JavaScript version - Remove all VW script references as they don't exist - Fixes issue where Install button only showed Load Script button after downloading VM/tools scripts --- src/server/services/scriptDownloader.js | 163 +++++++++++++++++++++--- 1 file changed, 142 insertions(+), 21 deletions(-) diff --git a/src/server/services/scriptDownloader.js b/src/server/services/scriptDownloader.js index 0a7ece0..c596d96 100644 --- a/src/server/services/scriptDownloader.js +++ b/src/server/services/scriptDownloader.js @@ -1,6 +1,6 @@ // Real JavaScript implementation for script downloading import { join } from 'path'; -import { writeFile, mkdir, access } from 'fs/promises'; +import { writeFile, mkdir, access, readFile } from 'fs/promises'; export class ScriptDownloaderService { constructor() { @@ -112,16 +112,6 @@ export class ScriptDownloaderService { await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir)); filePath = join(this.scriptsDirectory, finalTargetDir, fileName); await writeFile(filePath, content, 'utf-8'); - } else if (scriptPath.startsWith('vw/')) { - targetDir = 'vw'; - // Preserve subdirectory structure for VW scripts - const subPath = scriptPath.replace('vw/', ''); - const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; - finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; - // Ensure the subdirectory exists - await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir)); - filePath = join(this.scriptsDirectory, finalTargetDir, fileName); - await writeFile(filePath, content, 'utf-8'); } else { // Handle other script types (fallback to ct directory) targetDir = 'ct'; @@ -201,12 +191,6 @@ export class ScriptDownloaderService { const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; filePath = join(this.scriptsDirectory, finalTargetDir, fileName); - } else if (scriptPath.startsWith('vw/')) { - targetDir = 'vw'; - const subPath = scriptPath.replace('vw/', ''); - const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; - finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; - filePath = join(this.scriptsDirectory, finalTargetDir, fileName); } else { targetDir = 'ct'; finalTargetDir = targetDir; @@ -244,23 +228,39 @@ export class ScriptDownloaderService { if (fileName) { let targetDir; + let finalTargetDir; + let filePath; + if (scriptPath.startsWith('ct/')) { targetDir = 'ct'; + finalTargetDir = targetDir; + filePath = join(this.scriptsDirectory, targetDir, fileName); } else if (scriptPath.startsWith('tools/')) { targetDir = 'tools'; + // Preserve subdirectory structure for tools scripts + const subPath = scriptPath.replace('tools/', ''); + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; + filePath = join(this.scriptsDirectory, finalTargetDir, fileName); } else if (scriptPath.startsWith('vm/')) { targetDir = 'vm'; + // Preserve subdirectory structure for VM scripts + const subPath = scriptPath.replace('vm/', ''); + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; + filePath = join(this.scriptsDirectory, finalTargetDir, fileName); } else { targetDir = 'ct'; // Default fallback + finalTargetDir = targetDir; + filePath = join(this.scriptsDirectory, targetDir, fileName); } - const filePath = join(this.scriptsDirectory, targetDir, fileName); - try { await access(filePath); - files.push(`${targetDir}/${fileName}`); + files.push(`${finalTargetDir}/${fileName}`); - if (scriptPath.startsWith('ct/')) { + // Set ctExists for all script types (CT, tools, vm) for UI consistency + if (scriptPath.startsWith('ct/') || scriptPath.startsWith('tools/') || scriptPath.startsWith('vm/')) { ctExists = true; } } catch { @@ -292,6 +292,127 @@ export class ScriptDownloaderService { return { ctExists: false, installExists: false, files: [] }; } } + + async compareScriptContent(script) { + this.initializeConfig(); + const differences = []; + let hasDifferences = false; + + try { + // First check if any local files exist + const localFilesExist = await this.checkScriptExists(script); + if (!localFilesExist.ctExists && !localFilesExist.installExists) { + // No local files exist, so no comparison needed + return { hasDifferences: false, differences: [] }; + } + + // If we have local files, proceed with comparison + // Use Promise.all to run comparisons in parallel + const comparisonPromises = []; + + // Compare scripts only if they exist locally + if (localFilesExist.ctExists && script.install_methods?.length) { + for (const method of script.install_methods) { + if (method.script) { + const scriptPath = method.script; + const fileName = scriptPath.split('/').pop(); + + if (fileName) { + let targetDir; + let finalTargetDir; + + if (scriptPath.startsWith('ct/')) { + targetDir = 'ct'; + finalTargetDir = targetDir; + } else if (scriptPath.startsWith('tools/')) { + targetDir = 'tools'; + // Preserve subdirectory structure for tools scripts + const subPath = scriptPath.replace('tools/', ''); + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; + } else if (scriptPath.startsWith('vm/')) { + targetDir = 'vm'; + // Preserve subdirectory structure for VM scripts + const subPath = scriptPath.replace('vm/', ''); + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; + } else { + continue; // Skip unknown script types + } + + comparisonPromises.push( + this.compareSingleFile(scriptPath, `${finalTargetDir}/${fileName}`) + .then(result => { + if (result.hasDifferences) { + hasDifferences = true; + differences.push(result.filePath); + } + }) + .catch(() => { + // Don't add to differences if there's an error reading files + }) + ); + } + } + } + } + + // Compare install script only if it exists locally + if (localFilesExist.installExists) { + const installScriptName = `${script.slug}-install.sh`; + const installScriptPath = `install/${installScriptName}`; + + comparisonPromises.push( + this.compareSingleFile(installScriptPath, installScriptPath) + .then(result => { + if (result.hasDifferences) { + hasDifferences = true; + differences.push(result.filePath); + } + }) + .catch(() => { + // Don't add to differences if there's an error reading files + }) + ); + } + + // Wait for all comparisons to complete + await Promise.all(comparisonPromises); + + return { hasDifferences, differences }; + } catch (error) { + console.error('Error comparing script content:', error); + return { hasDifferences: false, differences: [] }; + } + } + + async compareSingleFile(remotePath, filePath) { + try { + const localPath = join(this.scriptsDirectory, filePath); + + // Read local content + const localContent = await readFile(localPath, 'utf-8'); + + // Download remote content + const remoteContent = await this.downloadFileFromGitHub(remotePath); + + // Apply modification only for CT scripts, not for other script types + let modifiedRemoteContent; + if (remotePath.startsWith('ct/')) { + modifiedRemoteContent = this.modifyScriptContent(remoteContent); + } else { + modifiedRemoteContent = remoteContent; // Don't modify tools or vm scripts + } + + // Compare content + const hasDifferences = localContent !== modifiedRemoteContent; + + return { hasDifferences, filePath }; + } catch (error) { + console.error(`Error comparing file ${filePath}:`, error); + return { hasDifferences: false, filePath }; + } + } } export const scriptDownloaderService = new ScriptDownloaderService();