fix: normalize script matching to handle underscore vs hyphen differences

- Add normalizeId helper to compare local filenames with script slugs/names
- Include install_basenames from install_methods for robust matching
- Fix false 'Not Downloaded' status for PVE Host scripts like 'PVE LXC Execute Command'
- Update DownloadedScriptsTab and ScriptsGrid to use normalized comparisons
- Resolves issue where scripts with underscores in filenames (e.g., pbs_microcode.sh)
  weren't matching JSON slugs with hyphens (e.g., pbs-microcode)
This commit is contained in:
auto-bot
2025-10-20 15:59:45 +02:00
parent adccee027c
commit 266ff5a79f
4 changed files with 40 additions and 6 deletions

View File

@@ -169,6 +169,13 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
// Update scripts with download status and filter to only downloaded scripts // Update scripts with download status and filter to only downloaded scripts
const downloadedScripts = React.useMemo((): ScriptCardType[] => { const downloadedScripts = React.useMemo((): ScriptCardType[] => {
// Helper to normalize identifiers so underscores vs hyphens don't break matches
const normalizeId = (s?: string): string => (s ?? '')
.toLowerCase()
.replace(/\.(sh|bash|py|js|ts)$/g, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return combinedScripts return combinedScripts
.map(script => { .map(script => {
if (!script?.name) { if (!script?.name) {
@@ -178,9 +185,13 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
// Check if there's a corresponding local script // Check if there's a corresponding local script
const hasLocalVersion = localScriptsData?.scripts?.some(local => { const hasLocalVersion = localScriptsData?.scripts?.some(local => {
if (!local?.name) return false; if (!local?.name) return false;
const localName = local.name.replace(/\.sh$/, ''); const normalizedLocal = normalizeId(local.name);
return localName.toLowerCase() === script.name.toLowerCase() || const matchesNameOrSlug = (
localName.toLowerCase() === (script.slug ?? '').toLowerCase(); normalizedLocal === normalizeId(script.name) ||
normalizedLocal === normalizeId(script.slug)
);
const matchesInstallBasename = (script as any)?.install_basenames?.some((base: string) => normalizeId(base) === normalizedLocal) ?? false;
return matchesNameOrSlug || matchesInstallBasename;
}) ?? false; }) ?? false;
return { return {

View File

@@ -200,6 +200,13 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
// Update scripts with download status // Update scripts with download status
const scriptsWithStatus = React.useMemo((): ScriptCardType[] => { const scriptsWithStatus = React.useMemo((): ScriptCardType[] => {
// Helper to normalize identifiers for robust matching
const normalizeId = (s?: string): string => (s ?? '')
.toLowerCase()
.replace(/\.(sh|bash|py|js|ts)$/g, '')
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return combinedScripts.map(script => { return combinedScripts.map(script => {
if (!script?.name) { if (!script?.name) {
return script; // Return as-is if invalid return script; // Return as-is if invalid
@@ -208,9 +215,13 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
// Check if there's a corresponding local script // Check if there's a corresponding local script
const hasLocalVersion = localScriptsData?.scripts?.some(local => { const hasLocalVersion = localScriptsData?.scripts?.some(local => {
if (!local?.name) return false; if (!local?.name) return false;
const localName = local.name.replace(/\.sh$/, ''); const normalizedLocal = normalizeId(local.name);
return localName.toLowerCase() === script.name.toLowerCase() || const matchesNameOrSlug = (
localName.toLowerCase() === (script.slug ?? '').toLowerCase(); normalizedLocal === normalizeId(script.name) ||
normalizedLocal === normalizeId(script.slug)
);
const matchesInstallBasename = (script as any)?.install_basenames?.some((base: string) => normalizeId(base) === normalizedLocal) ?? false;
return matchesNameOrSlug || matchesInstallBasename;
}) ?? false; }) ?? false;
return { return {

View File

@@ -177,6 +177,15 @@ export const scriptsRouter = createTRPCRouter({
const firstInstallMethod = script?.install_methods?.[0]; const firstInstallMethod = script?.install_methods?.[0];
const os = firstInstallMethod?.resources?.os; const os = firstInstallMethod?.resources?.os;
const version = firstInstallMethod?.resources?.version; const version = firstInstallMethod?.resources?.version;
// Extract install basenames for robust local matching (e.g., execute.sh -> execute)
const install_basenames = (script?.install_methods ?? [])
.map(m => m?.script)
.filter((p): p is string => typeof p === 'string')
.map(p => {
const parts = p.split('/');
const file = parts[parts.length - 1] ?? '';
return file.replace(/\.(sh|bash|py|js|ts)$/i, '');
});
return { return {
...card, ...card,
@@ -189,6 +198,7 @@ export const scriptsRouter = createTRPCRouter({
version: version, version: version,
// Add interface port // Add interface port
interface_port: script?.interface_port, interface_port: script?.interface_port,
install_basenames,
} as ScriptCard; } as ScriptCard;
}); });

View File

@@ -60,6 +60,8 @@ export interface ScriptCard {
os?: string; os?: string;
version?: string; version?: string;
interface_port?: number | null; interface_port?: number | null;
// Optional: basenames of install scripts (without extension)
install_basenames?: string[];
} }
export interface GitHubFile { export interface GitHubFile {