Files
bolt.diy/app/routes/api.github-template.ts
Nirmal Arya 6156b84019 Fix starter template import failure by adding required User-Agent headers
- Add User-Agent header to GitHub API requests (required by GitHub)
- Fix Cloudflare Workers environment context handling
- Add comprehensive logging for template import debugging
- Improve error handling with detailed error messages
- Verify fix works for all 13 starter templates

Resolves template import 500 errors where users saw "Failed to import starter template"

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-22 23:55:29 -04:00

120 lines
3.9 KiB
TypeScript

import { json } from '@remix-run/cloudflare';
import JSZip from 'jszip';
export async function loader({ request, context }: { request: Request; context?: any }) {
const url = new URL(request.url);
const repo = url.searchParams.get('repo');
if (!repo) {
return json({ error: 'Repository name is required' }, { status: 400 });
}
try {
const baseUrl = 'https://api.github.com';
// Get GitHub token from Cloudflare context env or fallback to process.env
const env = context?.cloudflare?.env || context?.env || process.env || {};
const githubToken = env.GITHUB_TOKEN;
console.log(`Processing GitHub template for repo: ${repo}`);
// Get the latest release
const releaseResponse = await fetch(`${baseUrl}/repos/${repo}/releases/latest`, {
headers: {
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'Buildify-App/1.0',
// Add GitHub token if available
...(githubToken ? { Authorization: `Bearer ${githubToken}` } : {}),
},
});
if (!releaseResponse.ok) {
const errorText = await releaseResponse.text();
console.error(`GitHub API error for repo ${repo}: ${releaseResponse.status} ${releaseResponse.statusText}`);
console.error(`Error details: ${errorText}`);
throw new Error(`GitHub API error: ${releaseResponse.status} - ${errorText}`);
}
const releaseData = (await releaseResponse.json()) as any;
const zipballUrl = releaseData.zipball_url;
if (!zipballUrl) {
throw new Error('No zipball URL found in release data');
}
console.log(`Fetching zipball from: ${zipballUrl}`);
// Fetch the zipball
const zipResponse = await fetch(zipballUrl, {
headers: {
'User-Agent': 'Buildify-App/1.0',
...(githubToken ? { Authorization: `Bearer ${githubToken}` } : {}),
},
});
if (!zipResponse.ok) {
const errorText = await zipResponse.text();
console.error(`Failed to fetch zipball: ${zipResponse.status} ${zipResponse.statusText}`);
console.error(`Error details: ${errorText}`);
throw new Error(`Failed to fetch release zipball: ${zipResponse.status} - ${errorText}`);
}
// Get the zip content as ArrayBuffer
const zipArrayBuffer = await zipResponse.arrayBuffer();
console.log(`Zip ArrayBuffer size: ${zipArrayBuffer.byteLength} bytes`);
// Use JSZip to extract the contents
const zip = await JSZip.loadAsync(zipArrayBuffer);
console.log('JSZip loaded successfully');
// Find the root folder name
let rootFolderName = '';
zip.forEach((relativePath) => {
if (!rootFolderName && relativePath.includes('/')) {
rootFolderName = relativePath.split('/')[0];
}
});
// Extract all files
const promises = Object.keys(zip.files).map(async (filename) => {
const zipEntry = zip.files[filename];
// Skip directories
if (zipEntry.dir) {
return null;
}
// Skip the root folder itself
if (filename === rootFolderName) {
return null;
}
// Remove the root folder from the path
let normalizedPath = filename;
if (rootFolderName && filename.startsWith(rootFolderName + '/')) {
normalizedPath = filename.substring(rootFolderName.length + 1);
}
// Get the file content
const content = await zipEntry.async('string');
return {
name: normalizedPath.split('/').pop() || '',
path: normalizedPath,
content,
};
});
const results = await Promise.all(promises);
const fileList = results.filter(Boolean) as { name: string; path: string; content: string }[];
console.log(`Successfully extracted ${fileList.length} files from ${repo}`);
return json(fileList);
} catch (error) {
console.error('Error processing GitHub template:', error);
return json({ error: 'Failed to fetch template files' }, { status: 500 });
}
}