Compare commits

...

5 Commits

Author SHA1 Message Date
Michel Roegl-Brunner
5d5eba72de fix: detect script changes from remote repository
- Add refetchOnMount and staleTime: 0 to compareScriptContent query to bypass React Query cache
- Add visible refresh button in script detail modal to manually check for updates
- Improve comparison error handling and logging for better debugging
- Display error messages in UI when comparison fails
- Ensure comparison always checks remote repository when modal opens
2025-11-26 08:32:13 +01:00
Michel Roegl-Brunner
577b96518e package-lock.json 2025-11-26 08:19:39 +01:00
Michel Roegl-Brunner
c6c27271d6 Merge pull request #342 from community-scripts/node24_securityfix 2025-11-24 21:34:06 +01:00
ProxmoxVE Developer
72c0246d8c chore(deps): upgrade to Next.js 16, Vitest 4, and Node.js 24
BREAKING CHANGES:
- Upgrade Next.js from 15.1.6 to 16.0.4
- Use Webpack instead of Turbopack for compatibility with server-side modules
- Upgrade Node.js requirement to >=24.0.0

FEATURES:
- Upgrade Vitest to 4.0.13 with improved testing capabilities
- Update all vitest-related packages (@vitest/ui, @vitest/coverage-v8)
- Upgrade react-syntax-highlighter to 16.1.0
- Update node-cron to 4.2.1
- Update lucide-react to 0.554.0

SECURITY:
- Resolve glob command injection vulnerability (CVE) via v10.5.0
- Fix all npm audit vulnerabilities (0 vulnerabilities found)
- Update prisma/client to 6.19.0
- Update all dependencies to latest secure versions

FIXES:
- Configure next.config.js for webpack with proper server-side module handling
- Update tsconfig.json for Next.js 16 compatibility (jsx: react-jsx)
- Add engines field to require Node.js >=24.0.0
- Remove deprecated webpack config in favor of Next.js 16 compatibility
2025-11-24 21:27:38 +01:00
github-actions[bot]
06d4786e0a chore: add VERSION v0.4.13 (#341)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-11-24 20:08:21 +00:00
7 changed files with 1138 additions and 1717 deletions

View File

@@ -1 +1 @@
0.4.12 0.4.13

View File

@@ -43,6 +43,10 @@ const config = {
'http://192.168.*', 'http://192.168.*',
], ],
turbopack: {
// Disable Turbopack and use Webpack instead for compatibility
// This is necessary for server-side code that uses child_process
},
webpack: (config, { dev, isServer }) => { webpack: (config, { dev, isServer }) => {
if (dev && !isServer) { if (dev && !isServer) {
config.watchOptions = { config.watchOptions = {
@@ -50,12 +54,15 @@ const config = {
aggregateTimeout: 300, aggregateTimeout: 300,
}; };
} }
// Handle server-side modules
if (isServer) {
config.externals = config.externals || [];
if (!config.externals.includes('child_process')) {
config.externals.push('child_process');
}
}
return config; return config;
}, },
// Ignore ESLint errors during build (they can be fixed separately)
eslint: {
ignoreDuringBuilds: true,
},
// Ignore TypeScript errors during build (they can be fixed separately) // Ignore TypeScript errors during build (they can be fixed separately)
typescript: { typescript: {
ignoreBuildErrors: true, ignoreBuildErrors: true,

2694
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,11 +4,11 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"build": "next build", "build": "next build --webpack",
"check": "next lint && tsc --noEmit", "check": "next lint && tsc --noEmit",
"dev": "next dev", "dev": "next dev --webpack",
"dev:server": "node server.js", "dev:server": "node server.js",
"dev:next": "next dev", "dev:next": "next dev --webpack",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache", "format:write": "prettier --write \"**/*.{ts,tsx,js,jsx,mdx}\" --cache",
"lint": "next lint", "lint": "next lint",
@@ -22,7 +22,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "^6.18.0", "@prisma/client": "^6.19.0",
"@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-slot": "^1.2.3",
"@t3-oss/env-nextjs": "^0.13.8", "@t3-oss/env-nextjs": "^0.13.8",
@@ -43,14 +43,14 @@
"cron-validator": "^1.2.0", "cron-validator": "^1.2.0",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lucide-react": "^0.553.0", "lucide-react": "^0.554.0",
"next": "^15.1.6", "next": "^16.0.4",
"node-cron": "^3.0.3", "node-cron": "^4.2.1",
"node-pty": "^1.0.0", "node-pty": "^1.0.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"react-syntax-highlighter": "^15.6.6", "react-syntax-highlighter": "^16.1.0",
"refractor": "^5.0.0", "refractor": "^5.0.0",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"server-only": "^0.0.1", "server-only": "^0.0.1",
@@ -74,10 +74,10 @@
"@types/react": "^19.2.4", "@types/react": "^19.2.4",
"@types/react-dom": "^19.2.2", "@types/react-dom": "^19.2.2",
"@vitejs/plugin-react": "^5.1.0", "@vitejs/plugin-react": "^5.1.0",
"@vitest/coverage-v8": "^3.2.4", "@vitest/coverage-v8": "^4.0.13",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^4.0.13",
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-config-next": "^15.1.6", "eslint-config-next": "^16.0.4",
"jsdom": "^27.2.0", "jsdom": "^27.2.0",
"postcss": "^8.5.3", "postcss": "^8.5.3",
"prettier": "^3.5.3", "prettier": "^3.5.3",
@@ -86,13 +86,16 @@
"tailwindcss": "^4.1.17", "tailwindcss": "^4.1.17",
"typescript": "^5.8.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.46.2", "typescript-eslint": "^8.46.2",
"vitest": "^3.2.4" "vitest": "^4.0.13"
}, },
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "7.39.3" "initVersion": "7.39.3"
}, },
"packageManager": "npm@10.9.3", "packageManager": "npm@10.9.3",
"engines": {
"node": ">=24.0.0"
},
"overrides": { "overrides": {
"prismjs": "^1.30.0" "prismjs": "^1.30.0"
} }
} }

View File

@@ -61,7 +61,11 @@ export function ScriptDetailModal({
isLoading: comparisonLoading, isLoading: comparisonLoading,
} = api.scripts.compareScriptContent.useQuery( } = api.scripts.compareScriptContent.useQuery(
{ slug: script?.slug ?? "" }, { slug: script?.slug ?? "" },
{ enabled: !!script && isOpen }, {
enabled: !!script && isOpen,
refetchOnMount: true,
staleTime: 0,
},
); );
// Load script mutation // Load script mutation
@@ -547,19 +551,60 @@ export function ScriptDetailModal({
</div> </div>
{scriptFilesData?.success && {scriptFilesData?.success &&
(scriptFilesData.ctExists || (scriptFilesData.ctExists ||
scriptFilesData.installExists) && scriptFilesData.installExists) && (
comparisonData?.success &&
!comparisonLoading && (
<div className="flex items-center space-x-2"> <div className="flex items-center space-x-2">
<div {comparisonData?.success ? (
className={`h-2 w-2 rounded-full ${comparisonData.hasDifferences ? "bg-warning" : "bg-success"}`} <>
></div> <div
<span> className={`h-2 w-2 rounded-full ${comparisonData.hasDifferences ? "bg-warning" : "bg-success"}`}
Status:{" "} ></div>
{comparisonData.hasDifferences <span>
? "Update available" Status:{" "}
: "Up to date"} {comparisonData.hasDifferences
</span> ? "Update available"
: "Up to date"}
</span>
</>
) : comparisonLoading ? (
<>
<div className="h-2 w-2 rounded-full bg-muted animate-pulse"></div>
<span>Checking for updates...</span>
</>
) : comparisonData?.error ? (
<>
<div className="h-2 w-2 rounded-full bg-destructive"></div>
<span className="text-destructive">Error: {comparisonData.error}</span>
</>
) : (
<>
<div className="h-2 w-2 rounded-full bg-muted"></div>
<span>Status: Unknown</span>
</>
)}
<button
onClick={() => void refetchComparison()}
disabled={comparisonLoading}
className="ml-2 p-1.5 rounded-md hover:bg-accent transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center"
title="Refresh comparison"
>
{comparisonLoading ? (
<div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent"></div>
) : (
<svg
className="h-4 w-4 text-muted-foreground hover:text-foreground"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
)}
</button>
</div> </div>
)} )}
</div> </div>

View File

@@ -519,13 +519,16 @@ export class ScriptDownloaderService {
comparisonPromises.push( comparisonPromises.push(
this.compareSingleFile(script, scriptPath, `${finalTargetDir}/${fileName}`) this.compareSingleFile(script, scriptPath, `${finalTargetDir}/${fileName}`)
.then(result => { .then(result => {
if (result.error) {
console.error(`[Comparison] Error comparing ${result.filePath}: ${result.error}`);
}
if (result.hasDifferences) { if (result.hasDifferences) {
hasDifferences = true; hasDifferences = true;
differences.push(result.filePath); differences.push(result.filePath);
} }
}) })
.catch(() => { .catch((error) => {
// Don't add to differences if there's an error reading files console.error(`[Comparison] Promise error for ${scriptPath}:`, error);
}) })
); );
} }
@@ -541,13 +544,16 @@ export class ScriptDownloaderService {
comparisonPromises.push( comparisonPromises.push(
this.compareSingleFile(script, installScriptPath, installScriptPath) this.compareSingleFile(script, installScriptPath, installScriptPath)
.then(result => { .then(result => {
if (result.error) {
console.error(`[Comparison] Error comparing ${result.filePath}: ${result.error}`);
}
if (result.hasDifferences) { if (result.hasDifferences) {
hasDifferences = true; hasDifferences = true;
differences.push(result.filePath); differences.push(result.filePath);
} }
}) })
.catch(() => { .catch((error) => {
// Don't add to differences if there's an error reading files console.error(`[Comparison] Promise error for ${installScriptPath}:`, error);
}) })
); );
} }
@@ -567,13 +573,16 @@ export class ScriptDownloaderService {
comparisonPromises.push( comparisonPromises.push(
this.compareSingleFile(script, alpineInstallScriptPath, alpineInstallScriptPath) this.compareSingleFile(script, alpineInstallScriptPath, alpineInstallScriptPath)
.then(result => { .then(result => {
if (result.error) {
console.error(`[Comparison] Error comparing ${result.filePath}: ${result.error}`);
}
if (result.hasDifferences) { if (result.hasDifferences) {
hasDifferences = true; hasDifferences = true;
differences.push(result.filePath); differences.push(result.filePath);
} }
}) })
.catch(() => { .catch((error) => {
// Don't add to differences if there's an error reading files console.error(`[Comparison] Promise error for ${alpineInstallScriptPath}:`, error);
}) })
); );
} catch { } catch {
@@ -584,10 +593,11 @@ export class ScriptDownloaderService {
// Wait for all comparisons to complete // Wait for all comparisons to complete
await Promise.all(comparisonPromises); await Promise.all(comparisonPromises);
console.log(`[Comparison] Completed comparison for ${script.slug}: hasDifferences=${hasDifferences}, differences=${differences.length}`);
return { hasDifferences, differences }; return { hasDifferences, differences };
} catch (error) { } catch (error) {
console.error('Error comparing script content:', error); console.error(`[Comparison] Error comparing script content for ${script.slug}:`, error);
return { hasDifferences: false, differences: [] }; return { hasDifferences: false, differences: [], error: error.message };
} }
} }
@@ -597,16 +607,21 @@ export class ScriptDownloaderService {
const repoUrl = this.getRepoUrlForScript(script); const repoUrl = this.getRepoUrlForScript(script);
const branch = process.env.REPO_BRANCH || 'main'; const branch = process.env.REPO_BRANCH || 'main';
console.log(`[Comparison] Comparing ${filePath} from ${repoUrl} (branch: ${branch})`);
// Read local content // Read local content
const localContent = await readFile(localPath, 'utf-8'); const localContent = await readFile(localPath, 'utf-8');
console.log(`[Comparison] Local file size: ${localContent.length} bytes`);
// Download remote content from the script's repository // Download remote content from the script's repository
const remoteContent = await this.downloadFileFromGitHub(repoUrl, remotePath, branch); const remoteContent = await this.downloadFileFromGitHub(repoUrl, remotePath, branch);
console.log(`[Comparison] Remote file size: ${remoteContent.length} bytes`);
// Apply modification only for CT scripts, not for other script types // Apply modification only for CT scripts, not for other script types
let modifiedRemoteContent; let modifiedRemoteContent;
if (remotePath.startsWith('ct/')) { if (remotePath.startsWith('ct/')) {
modifiedRemoteContent = this.modifyScriptContent(remoteContent); modifiedRemoteContent = this.modifyScriptContent(remoteContent);
console.log(`[Comparison] Applied CT script modifications`);
} else { } else {
modifiedRemoteContent = remoteContent; // Don't modify tools or vm scripts modifiedRemoteContent = remoteContent; // Don't modify tools or vm scripts
} }
@@ -614,10 +629,17 @@ export class ScriptDownloaderService {
// Compare content // Compare content
const hasDifferences = localContent !== modifiedRemoteContent; const hasDifferences = localContent !== modifiedRemoteContent;
if (hasDifferences) {
console.log(`[Comparison] Differences found in ${filePath}`);
} else {
console.log(`[Comparison] No differences in ${filePath}`);
}
return { hasDifferences, filePath }; return { hasDifferences, filePath };
} catch (error) { } catch (error) {
console.error(`Error comparing file ${filePath}:`, error); console.error(`[Comparison] Error comparing file ${filePath}:`, error.message);
return { hasDifferences: false, filePath }; // Return error information so it can be handled upstream
return { hasDifferences: false, filePath, error: error.message };
} }
} }

View File

@@ -22,7 +22,7 @@
"noEmit": true, "noEmit": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"jsx": "preserve", "jsx": "react-jsx",
"plugins": [ "plugins": [
{ {
"name": "next" "name": "next"