From e3e4556f83384b55d213574abf403d5ad7d447c8 Mon Sep 17 00:00:00 2001 From: CanbiZ <47820557+MickLesk@users.noreply.github.com> Date: Fri, 28 Nov 2025 13:06:16 +0100 Subject: [PATCH] Add type annotations and improve script services Enhanced type safety and documentation in several files, including adding explicit type annotations for script objects and function parameters. Improved error handling and code clarity in scriptDownloader.js, and updated autoSyncService.js to remove unnecessary cron job options. Refactored prisma.config.ts for schema configuration and updated server.js to support backup storage and improve parameter defaults. --- prisma.config.ts | 6 ++-- server.js | 9 ++++-- src/app/page.tsx | 14 +++------ src/server/services/autoSyncService.js | 3 -- src/server/services/scriptDownloader.js | 41 ++++++++++++++++++++++++- 5 files changed, 54 insertions(+), 19 deletions(-) diff --git a/prisma.config.ts b/prisma.config.ts index 6215f8b..bb69f76 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -2,9 +2,9 @@ import 'dotenv/config' import { defineConfig } from 'prisma/config' export default defineConfig({ - schema: 'prisma/schema.prisma', - migrate: { - migrations: 'prisma/migrations', + schema: { + kind: 'single', + filePath: 'prisma/schema.prisma', }, studio: { adapter: async () => { diff --git a/server.js b/server.js index fbc32fd..9350292 100644 --- a/server.js +++ b/server.js @@ -71,7 +71,10 @@ const handle = app.getRequestHandler(); * @property {ServerInfo} [server] * @property {boolean} [isUpdate] * @property {boolean} [isShell] + * @property {boolean} [isBackup] * @property {string} [containerId] + * @property {string} [storage] + * @property {string} [backupStorage] */ class ScriptExecutionHandler { @@ -720,7 +723,7 @@ class ScriptExecutionHandler { * @param {ServerInfo} server * @param {Function} [onComplete] - Optional callback when backup completes */ - startSSHBackupExecution(ws, containerId, executionId, storage, server, onComplete = null) { + startSSHBackupExecution(ws, containerId, executionId, storage, server, onComplete = undefined) { const sshService = getSSHExecutionService(); return new Promise((resolve, reject) => { @@ -832,10 +835,10 @@ class ScriptExecutionHandler { * @param {string} containerId * @param {string} executionId * @param {string} mode - * @param {ServerInfo|null} server + * @param {ServerInfo|undefined} server * @param {string} [backupStorage] - Optional storage to backup to before update */ - async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = null, backupStorage = null) { + async startUpdateExecution(ws, containerId, executionId, mode = 'local', server = undefined, backupStorage = undefined) { try { // If backup storage is provided, run backup first if (backupStorage && mode === 'ssh' && server) { diff --git a/src/app/page.tsx b/src/app/page.tsx index bedc4be..10ee292 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -23,6 +23,7 @@ import { Package, HardDrive, FolderOpen, LogOut, Archive } from "lucide-react"; import { api } from "~/trpc/react"; import { useAuth } from "./_components/AuthProvider"; import type { Server } from "~/types/server"; +import type { ScriptCard } from "~/types/script"; export default function Home() { const { isAuthenticated, logout } = useAuth(); @@ -101,9 +102,9 @@ export default function Home() { if (!scriptCardsData?.success) return 0; // Deduplicate scripts using Map by slug (same logic as ScriptsGrid.tsx) - const scriptMap = new Map(); + const scriptMap = new Map(); - scriptCardsData.cards?.forEach((script) => { + scriptCardsData.cards?.forEach((script: ScriptCard) => { if (script?.name && script?.slug) { // Use slug as unique identifier, only keep first occurrence if (!scriptMap.has(script.slug)) { @@ -126,17 +127,12 @@ export default function Home() { .replace(/^-+|-+$/g, ""); // First deduplicate GitHub scripts using Map by slug - interface ScriptCard { - name?: string; - slug?: string; - install_basenames?: string[]; - } const scriptMap = new Map(); - scriptCardsData.cards?.forEach((script) => { + scriptCardsData.cards?.forEach((script: ScriptCard) => { if (script?.name && script?.slug) { if (!scriptMap.has(script.slug)) { - scriptMap.set(script.slug, script as ScriptCard); + scriptMap.set(script.slug, script); } } }); diff --git a/src/server/services/autoSyncService.js b/src/server/services/autoSyncService.js index 49cf6ad..26370ff 100644 --- a/src/server/services/autoSyncService.js +++ b/src/server/services/autoSyncService.js @@ -300,9 +300,6 @@ export class AutoSyncService { console.log('Starting scheduled auto-sync...'); await this.executeAutoSync(); - }, { - scheduled: true, - timezone: 'UTC' }); console.log('Auto-sync cron job scheduled successfully'); diff --git a/src/server/services/scriptDownloader.js b/src/server/services/scriptDownloader.js index 0ba523c..cb2e12c 100644 --- a/src/server/services/scriptDownloader.js +++ b/src/server/services/scriptDownloader.js @@ -19,6 +19,8 @@ export class ScriptDownloaderService { /** * Validates that a directory path doesn't contain nested directories with the same name * (e.g., prevents ct/ct or install/install) + * @param {string} dirPath - The directory path to validate + * @returns {boolean} */ validateDirectoryPath(dirPath) { const normalizedPath = dirPath.replace(/\\/g, '/'); @@ -36,6 +38,9 @@ export class ScriptDownloaderService { /** * Validates that finalTargetDir doesn't contain nested directory names like ct/ct or install/install + * @param {string} targetDir - The base target directory + * @param {string} finalTargetDir - The final target directory to validate + * @returns {string} */ validateTargetDir(targetDir, finalTargetDir) { // Check if finalTargetDir contains nested directory names @@ -53,6 +58,11 @@ export class ScriptDownloaderService { return finalTargetDir; } + /** + * Ensure a directory exists, creating it if necessary + * @param {string} dirPath - The directory path to ensure exists + * @returns {Promise} + */ async ensureDirectoryExists(dirPath) { // Validate the directory path to prevent nested directories with the same name this.validateDirectoryPath(dirPath); @@ -61,7 +71,7 @@ export class ScriptDownloaderService { console.log(`[Directory Creation] Ensuring directory exists: ${dirPath}`); await mkdir(dirPath, { recursive: true }); console.log(`[Directory Creation] Directory created/verified: ${dirPath}`); - } catch (error) { + } catch (/** @type {any} */ error) { if (error.code !== 'EEXIST') { console.error(`[Directory Creation] Error creating directory ${dirPath}:`, error.message); throw error; @@ -71,6 +81,11 @@ export class ScriptDownloaderService { } } + /** + * Extract repository path from GitHub URL + * @param {string} repoUrl - The GitHub repository URL + * @returns {string} + */ extractRepoPath(repoUrl) { const match = /github\.com\/([^\/]+)\/([^\/]+)/.exec(repoUrl); if (!match) { @@ -79,6 +94,13 @@ export class ScriptDownloaderService { return `${match[1]}/${match[2]}`; } + /** + * Download a file from GitHub + * @param {string} repoUrl - The GitHub repository URL + * @param {string} filePath - The file path within the repository + * @param {string} [branch] - The branch to download from + * @returns {Promise} + */ async downloadFileFromGitHub(repoUrl, filePath, branch = 'main') { this.initializeConfig(); if (!repoUrl) { @@ -88,6 +110,7 @@ export class ScriptDownloaderService { const repoPath = this.extractRepoPath(repoUrl); const url = `https://raw.githubusercontent.com/${repoPath}/${branch}/${filePath}`; + /** @type {Record} */ const headers = { 'User-Agent': 'PVEScripts-Local/1.0', }; @@ -106,6 +129,11 @@ export class ScriptDownloaderService { return response.text(); } + /** + * Get repository URL for a script + * @param {import('~/types/script').Script} script - The script object + * @returns {string} + */ getRepoUrlForScript(script) { // Use repository_url from script if available, otherwise fallback to env or default if (script.repository_url) { @@ -115,6 +143,11 @@ export class ScriptDownloaderService { return this.repoUrl; } + /** + * Modify script content to use local paths + * @param {string} content - The script content + * @returns {string} + */ modifyScriptContent(content) { // Replace the build.func source line const oldPattern = /source <\(curl -fsSL https:\/\/raw\.githubusercontent\.com\/community-scripts\/ProxmoxVE\/main\/misc\/build\.func\)/g; @@ -123,9 +156,15 @@ export class ScriptDownloaderService { return content.replace(oldPattern, newPattern); } + /** + * Load a script by downloading its files + * @param {import('~/types/script').Script} script - The script to load + * @returns {Promise<{success: boolean, message: string, files: string[], error?: string}>} + */ async loadScript(script) { this.initializeConfig(); try { + /** @type {string[]} */ const files = []; const repoUrl = this.getRepoUrlForScript(script); const branch = process.env.REPO_BRANCH || 'main';