Fix: Detect downloaded scripts from all directories (ct, tools, vm, vw) (#120)

* Fix: Detect downloaded scripts from all directories (ct, tools, vm, vw)

- Add getAllDownloadedScripts() method to scan all script directories
- Update DownloadedScriptsTab to use new API endpoint
- Update ScriptsGrid to use new API endpoint for download status detection
- Update main page script counts to use new API endpoint
- Add recursive directory scanning to handle subdirectories
- Maintain backward compatibility with existing getCtScripts endpoint

Fixes issue where scripts downloaded to tools/, vm/, or vw/ directories
were not showing in Downloaded Scripts tab or showing correct download
status in Available Scripts tab.

* Fix: Remove redundant type annotation and method call arguments

- Remove redundant string type annotation from relativePath parameter
- Fix getScriptsFromDirectory method call to match updated signature
This commit is contained in:
Michel Roegl-Brunner
2025-10-13 13:38:58 +02:00
committed by GitHub
parent c12c96cfb9
commit 9d83697d45
5 changed files with 102 additions and 3 deletions

View File

@@ -37,7 +37,7 @@ export function DownloadedScriptsTab({ onInstallScript }: DownloadedScriptsTabPr
const gridRef = useRef<HTMLDivElement>(null); const gridRef = useRef<HTMLDivElement>(null);
const { data: scriptCardsData, isLoading: githubLoading, error: githubError, refetch } = api.scripts.getScriptCardsWithCategories.useQuery(); const { data: scriptCardsData, isLoading: githubLoading, error: githubError, refetch } = api.scripts.getScriptCardsWithCategories.useQuery();
const { data: localScriptsData, isLoading: localLoading, error: localError } = api.scripts.getCtScripts.useQuery(); const { data: localScriptsData, isLoading: localLoading, error: localError } = api.scripts.getAllDownloadedScripts.useQuery();
const { data: scriptData } = api.scripts.getScriptBySlug.useQuery( const { data: scriptData } = api.scripts.getScriptBySlug.useQuery(
{ slug: selectedSlug ?? '' }, { slug: selectedSlug ?? '' },
{ enabled: !!selectedSlug } { enabled: !!selectedSlug }

View File

@@ -34,7 +34,7 @@ export function ScriptsGrid({ onInstallScript }: ScriptsGridProps) {
const gridRef = useRef<HTMLDivElement>(null); const gridRef = useRef<HTMLDivElement>(null);
const { data: scriptCardsData, isLoading: githubLoading, error: githubError, refetch } = api.scripts.getScriptCardsWithCategories.useQuery(); const { data: scriptCardsData, isLoading: githubLoading, error: githubError, refetch } = api.scripts.getScriptCardsWithCategories.useQuery();
const { data: localScriptsData, isLoading: localLoading, error: localError } = api.scripts.getCtScripts.useQuery(); const { data: localScriptsData, isLoading: localLoading, error: localError } = api.scripts.getAllDownloadedScripts.useQuery();
const { data: scriptData } = api.scripts.getScriptBySlug.useQuery( const { data: scriptData } = api.scripts.getScriptBySlug.useQuery(
{ slug: selectedSlug ?? '' }, { slug: selectedSlug ?? '' },
{ enabled: !!selectedSlug } { enabled: !!selectedSlug }

View File

@@ -21,7 +21,7 @@ export default function Home() {
// Fetch data for script counts // Fetch data for script counts
const { data: scriptCardsData } = api.scripts.getScriptCardsWithCategories.useQuery(); const { data: scriptCardsData } = api.scripts.getScriptCardsWithCategories.useQuery();
const { data: localScriptsData } = api.scripts.getCtScripts.useQuery(); const { data: localScriptsData } = api.scripts.getAllDownloadedScripts.useQuery();
const { data: installedScriptsData } = api.installedScripts.getAllInstalledScripts.useQuery(); const { data: installedScriptsData } = api.installedScripts.getAllInstalledScripts.useQuery();
// Calculate script counts // Calculate script counts

View File

@@ -27,6 +27,16 @@ export const scriptsRouter = createTRPCRouter({
}; };
}), }),
// Get all downloaded scripts from all directories
getAllDownloadedScripts: publicProcedure
.query(async () => {
const scripts = await scriptManager.getAllDownloadedScripts();
return {
scripts,
directoryInfo: scriptManager.getScriptsDirectoryInfo()
};
}),
// Get script content for viewing // Get script content for viewing
getScriptContent: publicProcedure getScriptContent: publicProcedure

View File

@@ -141,6 +141,95 @@ export class ScriptManager {
} }
} }
/**
* Get all downloaded scripts from all directories (ct, tools, vm, vw)
*/
async getAllDownloadedScripts(): Promise<ScriptInfo[]> {
this.initializeConfig();
const allScripts: ScriptInfo[] = [];
// Define all script directories to scan
const scriptDirs = ['ct', 'tools', 'vm', 'vw'];
for (const dirName of scriptDirs) {
try {
const dirPath = join(this.scriptsDir!, dirName);
// Check if directory exists
try {
await stat(dirPath);
} catch {
// Directory doesn't exist, skip it
continue;
}
const scripts = await this.getScriptsFromDirectory(dirPath);
allScripts.push(...scripts);
} catch (error) {
console.error(`Error reading ${dirName} scripts directory:`, error);
// Continue with other directories even if one fails
}
}
return allScripts.sort((a, b) => a.name.localeCompare(b.name));
}
/**
* Get scripts from a specific directory (recursively)
*/
private async getScriptsFromDirectory(dirPath: string): Promise<ScriptInfo[]> {
const scripts: ScriptInfo[] = [];
const scanDirectory = async (currentPath: string, relativePath = ''): Promise<void> => {
const files = await readdir(currentPath);
for (const file of files) {
const filePath = join(currentPath, file);
const stats = await stat(filePath);
if (stats.isFile()) {
const extension = extname(file);
// Check if file extension is allowed
if (this.allowedExtensions!.includes(extension)) {
// Check if file is executable
const executable = await this.isExecutable(filePath);
// Extract slug from filename (remove .sh extension)
const slug = file.replace(/\.sh$/, '');
// Try to get logo from JSON data
let logo: string | undefined;
try {
const scriptData = await localScriptsService.getScriptBySlug(slug);
logo = scriptData?.logo ?? undefined;
} catch {
// JSON file might not exist, that's okay
}
scripts.push({
name: file,
path: filePath,
extension,
size: stats.size,
lastModified: stats.mtime,
executable,
logo,
slug
});
}
} else if (stats.isDirectory()) {
// Recursively scan subdirectories
const subRelativePath = relativePath ? join(relativePath, file) : file;
await scanDirectory(filePath, subRelativePath);
}
}
};
await scanDirectory(dirPath);
return scripts;
}
/** /**
* Check if a file is executable * Check if a file is executable
*/ */