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';