fix(types): add runtime validation for GitHub API response in skill-hub-routes

- Replace GitHubTreeEntry with GitHubContentEntry matching actual API response
- Add runtime array validation to prevent unexpected response formats
- Normalize GitHub API types ('file'/'dir') to internal types ('blob'/'tree')
- Validate required fields (name, path, type) for each entry

This fixes potential runtime errors when GitHub API response structure
differs from expected type definition.
This commit is contained in:
catlog22
2026-02-26 10:28:33 +08:00
parent b6f4864530
commit bf02f653ca

View File

@@ -462,20 +462,37 @@ function buildDownloadUrlFromPath(skillPath: string): string {
}
/**
* Fetch skill directory contents from GitHub API
* Returns list of files in the directory
* GitHub Contents API response entry
* @see https://docs.github.com/en/rest/repos/contents#get-repository-content
*/
interface GitHubTreeEntry {
interface GitHubContentEntry {
name: string;
path: string;
sha: string;
size?: number;
type: 'file' | 'dir' | 'submodule' | 'symlink';
download_url?: string;
url: string;
html_url?: string;
git_url?: string;
}
/**
* Internal normalized entry type for processing
*/
interface NormalizedTreeEntry {
path: string;
mode: string;
type: 'blob' | 'tree';
sha: string;
size?: number;
url: string;
}
async function fetchSkillDirectoryContents(skillPath: string): Promise<GitHubTreeEntry[]> {
// Use GitHub API to get tree contents
/**
* Fetch skill directory contents from GitHub Contents API
* Returns normalized list of files and directories
*/
async function fetchSkillDirectoryContents(skillPath: string): Promise<NormalizedTreeEntry[]> {
const apiUrl = `https://api.github.com/repos/${GITHUB_CONFIG.owner}/${GITHUB_CONFIG.repo}/contents/${skillPath}?ref=${GITHUB_CONFIG.branch}`;
const response = await fetch(apiUrl, {
@@ -489,7 +506,30 @@ async function fetchSkillDirectoryContents(skillPath: string): Promise<GitHubTre
throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
}
return response.json() as Promise<GitHubTreeEntry[]>;
const contents = await response.json();
// Runtime validation: must be an array
if (!Array.isArray(contents)) {
throw new Error(`Unexpected GitHub API response for directory: ${skillPath}`);
}
// Normalize and validate each entry
return contents.map((entry: GitHubContentEntry) => {
if (!entry.name || !entry.path || !entry.type) {
throw new Error(`Invalid GitHub API entry: ${JSON.stringify(entry)}`);
}
// Normalize GitHub type to internal type
const normalizedType: 'blob' | 'tree' = entry.type === 'dir' ? 'tree' : 'blob';
return {
path: entry.path,
type: normalizedType,
sha: entry.sha,
size: entry.size,
url: entry.url,
};
});
}
/**