|
1 | 1 | // Real JavaScript implementation for script downloading |
2 | 2 | import { join } from 'path'; |
3 | | -import { writeFile, mkdir, access } from 'fs/promises'; |
| 3 | +import { writeFile, mkdir, access, readFile } from 'fs/promises'; |
4 | 4 |
|
5 | 5 | export class ScriptDownloaderService { |
6 | 6 | constructor() { |
@@ -112,16 +112,6 @@ export class ScriptDownloaderService { |
112 | 112 | await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir)); |
113 | 113 | filePath = join(this.scriptsDirectory, finalTargetDir, fileName); |
114 | 114 | await writeFile(filePath, content, 'utf-8'); |
115 | | - } else if (scriptPath.startsWith('vw/')) { |
116 | | - targetDir = 'vw'; |
117 | | - // Preserve subdirectory structure for VW scripts |
118 | | - const subPath = scriptPath.replace('vw/', ''); |
119 | | - const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; |
120 | | - finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; |
121 | | - // Ensure the subdirectory exists |
122 | | - await this.ensureDirectoryExists(join(this.scriptsDirectory, finalTargetDir)); |
123 | | - filePath = join(this.scriptsDirectory, finalTargetDir, fileName); |
124 | | - await writeFile(filePath, content, 'utf-8'); |
125 | 115 | } else { |
126 | 116 | // Handle other script types (fallback to ct directory) |
127 | 117 | targetDir = 'ct'; |
@@ -201,12 +191,6 @@ export class ScriptDownloaderService { |
201 | 191 | const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; |
202 | 192 | finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; |
203 | 193 | filePath = join(this.scriptsDirectory, finalTargetDir, fileName); |
204 | | - } else if (scriptPath.startsWith('vw/')) { |
205 | | - targetDir = 'vw'; |
206 | | - const subPath = scriptPath.replace('vw/', ''); |
207 | | - const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; |
208 | | - finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; |
209 | | - filePath = join(this.scriptsDirectory, finalTargetDir, fileName); |
210 | 194 | } else { |
211 | 195 | targetDir = 'ct'; |
212 | 196 | finalTargetDir = targetDir; |
@@ -244,23 +228,39 @@ export class ScriptDownloaderService { |
244 | 228 |
|
245 | 229 | if (fileName) { |
246 | 230 | let targetDir; |
| 231 | + let finalTargetDir; |
| 232 | + let filePath; |
| 233 | + |
247 | 234 | if (scriptPath.startsWith('ct/')) { |
248 | 235 | targetDir = 'ct'; |
| 236 | + finalTargetDir = targetDir; |
| 237 | + filePath = join(this.scriptsDirectory, targetDir, fileName); |
249 | 238 | } else if (scriptPath.startsWith('tools/')) { |
250 | 239 | targetDir = 'tools'; |
| 240 | + // Preserve subdirectory structure for tools scripts |
| 241 | + const subPath = scriptPath.replace('tools/', ''); |
| 242 | + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; |
| 243 | + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; |
| 244 | + filePath = join(this.scriptsDirectory, finalTargetDir, fileName); |
251 | 245 | } else if (scriptPath.startsWith('vm/')) { |
252 | 246 | targetDir = 'vm'; |
| 247 | + // Preserve subdirectory structure for VM scripts |
| 248 | + const subPath = scriptPath.replace('vm/', ''); |
| 249 | + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; |
| 250 | + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; |
| 251 | + filePath = join(this.scriptsDirectory, finalTargetDir, fileName); |
253 | 252 | } else { |
254 | 253 | targetDir = 'ct'; // Default fallback |
| 254 | + finalTargetDir = targetDir; |
| 255 | + filePath = join(this.scriptsDirectory, targetDir, fileName); |
255 | 256 | } |
256 | 257 |
|
257 | | - const filePath = join(this.scriptsDirectory, targetDir, fileName); |
258 | | - |
259 | 258 | try { |
260 | 259 | await access(filePath); |
261 | | - files.push(`${targetDir}/${fileName}`); |
| 260 | + files.push(`${finalTargetDir}/${fileName}`); |
262 | 261 |
|
263 | | - if (scriptPath.startsWith('ct/')) { |
| 262 | + // Set ctExists for all script types (CT, tools, vm) for UI consistency |
| 263 | + if (scriptPath.startsWith('ct/') || scriptPath.startsWith('tools/') || scriptPath.startsWith('vm/')) { |
264 | 264 | ctExists = true; |
265 | 265 | } |
266 | 266 | } catch { |
@@ -292,6 +292,127 @@ export class ScriptDownloaderService { |
292 | 292 | return { ctExists: false, installExists: false, files: [] }; |
293 | 293 | } |
294 | 294 | } |
| 295 | + |
| 296 | + async compareScriptContent(script) { |
| 297 | + this.initializeConfig(); |
| 298 | + const differences = []; |
| 299 | + let hasDifferences = false; |
| 300 | + |
| 301 | + try { |
| 302 | + // First check if any local files exist |
| 303 | + const localFilesExist = await this.checkScriptExists(script); |
| 304 | + if (!localFilesExist.ctExists && !localFilesExist.installExists) { |
| 305 | + // No local files exist, so no comparison needed |
| 306 | + return { hasDifferences: false, differences: [] }; |
| 307 | + } |
| 308 | + |
| 309 | + // If we have local files, proceed with comparison |
| 310 | + // Use Promise.all to run comparisons in parallel |
| 311 | + const comparisonPromises = []; |
| 312 | + |
| 313 | + // Compare scripts only if they exist locally |
| 314 | + if (localFilesExist.ctExists && script.install_methods?.length) { |
| 315 | + for (const method of script.install_methods) { |
| 316 | + if (method.script) { |
| 317 | + const scriptPath = method.script; |
| 318 | + const fileName = scriptPath.split('/').pop(); |
| 319 | + |
| 320 | + if (fileName) { |
| 321 | + let targetDir; |
| 322 | + let finalTargetDir; |
| 323 | + |
| 324 | + if (scriptPath.startsWith('ct/')) { |
| 325 | + targetDir = 'ct'; |
| 326 | + finalTargetDir = targetDir; |
| 327 | + } else if (scriptPath.startsWith('tools/')) { |
| 328 | + targetDir = 'tools'; |
| 329 | + // Preserve subdirectory structure for tools scripts |
| 330 | + const subPath = scriptPath.replace('tools/', ''); |
| 331 | + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; |
| 332 | + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; |
| 333 | + } else if (scriptPath.startsWith('vm/')) { |
| 334 | + targetDir = 'vm'; |
| 335 | + // Preserve subdirectory structure for VM scripts |
| 336 | + const subPath = scriptPath.replace('vm/', ''); |
| 337 | + const subDir = subPath.includes('/') ? subPath.substring(0, subPath.lastIndexOf('/')) : ''; |
| 338 | + finalTargetDir = subDir ? join(targetDir, subDir) : targetDir; |
| 339 | + } else { |
| 340 | + continue; // Skip unknown script types |
| 341 | + } |
| 342 | + |
| 343 | + comparisonPromises.push( |
| 344 | + this.compareSingleFile(scriptPath, `${finalTargetDir}/${fileName}`) |
| 345 | + .then(result => { |
| 346 | + if (result.hasDifferences) { |
| 347 | + hasDifferences = true; |
| 348 | + differences.push(result.filePath); |
| 349 | + } |
| 350 | + }) |
| 351 | + .catch(() => { |
| 352 | + // Don't add to differences if there's an error reading files |
| 353 | + }) |
| 354 | + ); |
| 355 | + } |
| 356 | + } |
| 357 | + } |
| 358 | + } |
| 359 | + |
| 360 | + // Compare install script only if it exists locally |
| 361 | + if (localFilesExist.installExists) { |
| 362 | + const installScriptName = `${script.slug}-install.sh`; |
| 363 | + const installScriptPath = `install/${installScriptName}`; |
| 364 | + |
| 365 | + comparisonPromises.push( |
| 366 | + this.compareSingleFile(installScriptPath, installScriptPath) |
| 367 | + .then(result => { |
| 368 | + if (result.hasDifferences) { |
| 369 | + hasDifferences = true; |
| 370 | + differences.push(result.filePath); |
| 371 | + } |
| 372 | + }) |
| 373 | + .catch(() => { |
| 374 | + // Don't add to differences if there's an error reading files |
| 375 | + }) |
| 376 | + ); |
| 377 | + } |
| 378 | + |
| 379 | + // Wait for all comparisons to complete |
| 380 | + await Promise.all(comparisonPromises); |
| 381 | + |
| 382 | + return { hasDifferences, differences }; |
| 383 | + } catch (error) { |
| 384 | + console.error('Error comparing script content:', error); |
| 385 | + return { hasDifferences: false, differences: [] }; |
| 386 | + } |
| 387 | + } |
| 388 | + |
| 389 | + async compareSingleFile(remotePath, filePath) { |
| 390 | + try { |
| 391 | + const localPath = join(this.scriptsDirectory, filePath); |
| 392 | + |
| 393 | + // Read local content |
| 394 | + const localContent = await readFile(localPath, 'utf-8'); |
| 395 | + |
| 396 | + // Download remote content |
| 397 | + const remoteContent = await this.downloadFileFromGitHub(remotePath); |
| 398 | + |
| 399 | + // Apply modification only for CT scripts, not for other script types |
| 400 | + let modifiedRemoteContent; |
| 401 | + if (remotePath.startsWith('ct/')) { |
| 402 | + modifiedRemoteContent = this.modifyScriptContent(remoteContent); |
| 403 | + } else { |
| 404 | + modifiedRemoteContent = remoteContent; // Don't modify tools or vm scripts |
| 405 | + } |
| 406 | + |
| 407 | + // Compare content |
| 408 | + const hasDifferences = localContent !== modifiedRemoteContent; |
| 409 | + |
| 410 | + return { hasDifferences, filePath }; |
| 411 | + } catch (error) { |
| 412 | + console.error(`Error comparing file ${filePath}:`, error); |
| 413 | + return { hasDifferences: false, filePath }; |
| 414 | + } |
| 415 | + } |
295 | 416 | } |
296 | 417 |
|
297 | 418 | export const scriptDownloaderService = new ScriptDownloaderService(); |
0 commit comments