diff --git a/app/routes/api.github-template.ts b/app/routes/api.github-template.ts index 8f1679d0..09ab49da 100644 --- a/app/routes/api.github-template.ts +++ b/app/routes/api.github-template.ts @@ -1,19 +1,6 @@ import { json } from '@remix-run/cloudflare'; import JSZip from 'jszip'; -// Function to detect if we're running in Cloudflare -function isCloudflareEnvironment(context: any): boolean { - // Check if we're in production AND have Cloudflare Pages specific env vars - const isProduction = process.env.NODE_ENV === 'production'; - const hasCfPagesVars = !!( - context?.cloudflare?.env?.CF_PAGES || - context?.cloudflare?.env?.CF_PAGES_URL || - context?.cloudflare?.env?.CF_PAGES_COMMIT_SHA - ); - - return isProduction && hasCfPagesVars; -} - // Cloudflare-compatible method using GitHub Contents API async function fetchRepoContentsCloudflare(repo: string, githubToken?: string) { const baseUrl = 'https://api.github.com'; @@ -215,10 +202,14 @@ export async function loader({ request, context }: { request: Request; context: let fileList; - if (isCloudflareEnvironment(context)) { - fileList = await fetchRepoContentsCloudflare(repo, githubToken); - } else { + try { + // Try zip method first (uses latest release) fileList = await fetchRepoContentsZip(repo, githubToken); + } catch (zipError) { + console.warn('Zip method failed, falling back to Cloudflare method:', zipError); + + // Fallback to Cloudflare method (uses GitHub Contents API) + fileList = await fetchRepoContentsCloudflare(repo, githubToken); } // Filter out .git files for both methods diff --git a/app/shared/utils/selectStarterTemplate.ts b/app/shared/utils/selectStarterTemplate.ts index a71331bd..376432dc 100644 --- a/app/shared/utils/selectStarterTemplate.ts +++ b/app/shared/utils/selectStarterTemplate.ts @@ -1,6 +1,7 @@ import ignore from 'ignore'; import type { ProviderInfo } from '~/shared/types/model'; import type { Template } from '~/shared/types/template'; +import Cookies from 'js-cookie'; import { STARTER_TEMPLATES } from './constants'; const starterTemplateSelectionPrompt = (templates: Template[]) => ` @@ -114,7 +115,7 @@ export const selectStarterTemplate = async (options: { message: string; model: s const getGitHubRepoContent = async (repoName: string): Promise<{ name: string; path: string; content: string }[]> => { try { - // Instead of directly fetching from GitHub, use our own API endpoint as a proxy + // First try: Use our own API endpoint as a proxy const response = await fetch(`/api/github-template?repo=${encodeURIComponent(repoName)}`); if (!response.ok) { @@ -126,7 +127,90 @@ const getGitHubRepoContent = async (repoName: string): Promise<{ name: string; p return files; } catch (error) { - console.error('Error fetching release contents:', error); + console.error('Error fetching release contents via API route:', error); + console.log('Falling back to direct GitHub API call...'); + + // Fallback: Direct GitHub API call + return await getGitHubRepoContentDirect(repoName); + } +}; + +// Fallback method for direct GitHub API access +const getGitHubRepoContentDirect = async ( + repoName: string, + path: string = '', +): Promise<{ name: string; path: string; content: string }[]> => { + const baseUrl = 'https://api.github.com'; + + try { + const token = Cookies.get('githubToken') || import.meta.env.VITE_GITHUB_ACCESS_TOKEN; + + const headers: HeadersInit = { + Accept: 'application/vnd.github.v3+json', + }; + + // Add your GitHub token if needed + if (token) { + headers.Authorization = 'Bearer ' + token; + } + + // Fetch contents of the path + const response = await fetch(`${baseUrl}/repos/${repoName}/contents/${path}`, { + headers, + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: any = await response.json(); + + // If it's a single file, return its content + if (!Array.isArray(data)) { + if (data.type === 'file') { + // If it's a file, get its content + const content = atob(data.content); // Decode base64 content + return [ + { + name: data.name, + path: data.path, + content, + }, + ]; + } + } + + // Process directory contents recursively + const contents = await Promise.all( + data.map(async (item: any) => { + if (item.type === 'dir') { + // Recursively get contents of subdirectories + return await getGitHubRepoContentDirect(repoName, item.path); + } else if (item.type === 'file') { + // Fetch file content + const fileResponse = await fetch(item.url, { + headers, + }); + const fileData: any = await fileResponse.json(); + const content = atob(fileData.content); // Decode base64 content + + return [ + { + name: item.name, + path: item.path, + content, + }, + ]; + } + + return []; + }), + ); + + // Flatten the array of contents + return contents.flat(); + } catch (error) { + console.error('Error fetching repo contents directly from GitHub:', error); throw error; } };