feat: Switch to local JSON files as primary source with GitHub resync
- Create LocalScriptsService to read from scripts/json directory - Update tRPC routes to use local files by default - Implement resync functionality to download from GitHub and save locally - Make GitHub optional - only needed for resyncing - Update UI to reflect local-first approach - Add proper error handling for missing local files
This commit is contained in:
@@ -48,8 +48,8 @@ export function ScriptsGrid() {
|
||||
{scriptCardsData?.error || 'Unknown error occurred'}
|
||||
</p>
|
||||
<div className="mt-4 text-xs text-gray-400">
|
||||
<p>Make sure to set the REPO_URL environment variable.</p>
|
||||
<p>Example: REPO_URL="https://github.com/username/repo"</p>
|
||||
<p>No JSON files found in scripts/json directory.</p>
|
||||
<p>Use the "Resync Scripts" button to download from GitHub.</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
|
||||
@@ -50,7 +50,7 @@ export default function Home() {
|
||||
: 'text-gray-500 hover:text-gray-700'
|
||||
}`}
|
||||
>
|
||||
GitHub Scripts
|
||||
Script Library
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('local')}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { createTRPCRouter, publicProcedure } from "~/server/api/trpc";
|
||||
import { scriptManager } from "~/server/lib/scripts";
|
||||
import { gitManager } from "~/server/lib/git";
|
||||
import { githubService } from "~/server/services/github";
|
||||
import { localScriptsService } from "~/server/services/localScripts";
|
||||
|
||||
export const scriptsRouter = createTRPCRouter({
|
||||
// Get all available scripts
|
||||
@@ -58,12 +59,12 @@ export const scriptsRouter = createTRPCRouter({
|
||||
return scriptManager.getScriptsDirectoryInfo();
|
||||
}),
|
||||
|
||||
// GitHub-based script routes
|
||||
// Get all script cards from GitHub repo
|
||||
// Local script routes (using scripts/json directory)
|
||||
// Get all script cards from local directory
|
||||
getScriptCards: publicProcedure
|
||||
.query(async () => {
|
||||
try {
|
||||
const cards = await githubService.getScriptCards();
|
||||
const cards = await localScriptsService.getScriptCards();
|
||||
return { success: true, cards };
|
||||
} catch (error) {
|
||||
console.error('Error in getScriptCards:', error);
|
||||
@@ -75,11 +76,11 @@ export const scriptsRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
// Get all scripts from GitHub repo
|
||||
// Get all scripts from local directory
|
||||
getAllScripts: publicProcedure
|
||||
.query(async () => {
|
||||
try {
|
||||
const scripts = await githubService.getAllScripts();
|
||||
const scripts = await localScriptsService.getAllScripts();
|
||||
return { success: true, scripts };
|
||||
} catch (error) {
|
||||
return {
|
||||
@@ -90,12 +91,12 @@ export const scriptsRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
// Get script by slug from GitHub repo
|
||||
// Get script by slug from local directory
|
||||
getScriptBySlug: publicProcedure
|
||||
.input(z.object({ slug: z.string() }))
|
||||
.query(async ({ input }) => {
|
||||
try {
|
||||
const script = await githubService.getScriptBySlug(input.slug);
|
||||
const script = await localScriptsService.getScriptBySlug(input.slug);
|
||||
if (!script) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -113,20 +114,26 @@ export const scriptsRouter = createTRPCRouter({
|
||||
}
|
||||
}),
|
||||
|
||||
// Resync scripts from GitHub repo
|
||||
// Resync scripts from GitHub repo to local directory
|
||||
resyncScripts: publicProcedure
|
||||
.mutation(async () => {
|
||||
try {
|
||||
const scripts = await githubService.getAllScripts();
|
||||
// First, try to get scripts from GitHub
|
||||
const githubScripts = await githubService.getAllScripts();
|
||||
|
||||
// Save scripts to local directory
|
||||
await localScriptsService.saveScriptsFromGitHub(githubScripts);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: `Successfully synced ${scripts.length} scripts`,
|
||||
count: scripts.length
|
||||
message: `Successfully synced ${githubScripts.length} scripts from GitHub to local directory`,
|
||||
count: githubScripts.length
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error in resyncScripts:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to resync scripts',
|
||||
error: error instanceof Error ? error.message : 'Failed to resync scripts. Make sure REPO_URL is set.',
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
|
||||
106
src/server/services/localScripts.ts
Normal file
106
src/server/services/localScripts.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { readFile, readdir, writeFile, mkdir } from 'fs/promises';
|
||||
import { join } from 'path';
|
||||
import type { Script, ScriptCard } from '~/types/script';
|
||||
|
||||
export class LocalScriptsService {
|
||||
private scriptsDirectory: string;
|
||||
|
||||
constructor() {
|
||||
this.scriptsDirectory = join(process.cwd(), 'scripts', 'json');
|
||||
}
|
||||
|
||||
async getJsonFiles(): Promise<string[]> {
|
||||
try {
|
||||
const files = await readdir(this.scriptsDirectory);
|
||||
return files.filter(file => file.endsWith('.json'));
|
||||
} catch (error) {
|
||||
console.error('Error reading scripts directory:', error);
|
||||
throw new Error('Failed to read scripts directory');
|
||||
}
|
||||
}
|
||||
|
||||
async getScriptContent(filename: string): Promise<Script> {
|
||||
try {
|
||||
const filePath = join(this.scriptsDirectory, filename);
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
return JSON.parse(content) as Script;
|
||||
} catch (error) {
|
||||
console.error(`Error reading script file ${filename}:`, error);
|
||||
throw new Error(`Failed to read script: ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
async getAllScripts(): Promise<Script[]> {
|
||||
try {
|
||||
const jsonFiles = await this.getJsonFiles();
|
||||
const scripts: Script[] = [];
|
||||
|
||||
for (const filename of jsonFiles) {
|
||||
try {
|
||||
const script = await this.getScriptContent(filename);
|
||||
scripts.push(script);
|
||||
} catch (error) {
|
||||
console.error(`Failed to parse script ${filename}:`, error);
|
||||
// Continue with other files even if one fails
|
||||
}
|
||||
}
|
||||
|
||||
return scripts;
|
||||
} catch (error) {
|
||||
console.error('Error fetching all scripts:', error);
|
||||
throw new Error('Failed to fetch scripts from local directory');
|
||||
}
|
||||
}
|
||||
|
||||
async getScriptCards(): Promise<ScriptCard[]> {
|
||||
try {
|
||||
const scripts = await this.getAllScripts();
|
||||
|
||||
return scripts.map(script => ({
|
||||
name: script.name,
|
||||
slug: script.slug,
|
||||
description: script.description,
|
||||
logo: script.logo,
|
||||
type: script.type,
|
||||
updateable: script.updateable,
|
||||
website: script.website,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error creating script cards:', error);
|
||||
throw new Error('Failed to create script cards');
|
||||
}
|
||||
}
|
||||
|
||||
async getScriptBySlug(slug: string): Promise<Script | null> {
|
||||
try {
|
||||
const scripts = await this.getAllScripts();
|
||||
return scripts.find(script => script.slug === slug) || null;
|
||||
} catch (error) {
|
||||
console.error('Error fetching script by slug:', error);
|
||||
throw new Error(`Failed to fetch script: ${slug}`);
|
||||
}
|
||||
}
|
||||
|
||||
async saveScriptsFromGitHub(scripts: Script[]): Promise<void> {
|
||||
try {
|
||||
// Ensure the directory exists
|
||||
await mkdir(this.scriptsDirectory, { recursive: true });
|
||||
|
||||
// Save each script as a JSON file
|
||||
for (const script of scripts) {
|
||||
const filename = `${script.slug}.json`;
|
||||
const filePath = join(this.scriptsDirectory, filename);
|
||||
const content = JSON.stringify(script, null, 2);
|
||||
await writeFile(filePath, content, 'utf-8');
|
||||
}
|
||||
|
||||
console.log(`Successfully saved ${scripts.length} scripts to ${this.scriptsDirectory}`);
|
||||
} catch (error) {
|
||||
console.error('Error saving scripts from GitHub:', error);
|
||||
throw new Error('Failed to save scripts from GitHub');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const localScriptsService = new LocalScriptsService();
|
||||
Reference in New Issue
Block a user