fix: remove GitHub hardcoding and generalize for non-GitHub git providers (#1216)

* fix: remove GitHub hardcoding and generalize for non-GitHub git providers

* fix: guard SSH path extraction against missing colon separator

---------

Co-authored-by: artac <artac@artacs-MacBook-Pro.local>
This commit is contained in:
kjhq
2026-05-13 10:19:31 +03:00
committed by GitHub
co-authored by artac
parent 51c4ec693b
commit f059bc8dd6
4 changed files with 83 additions and 66 deletions
@@ -1,6 +1,7 @@
const GITHUB_HOST = 'github.com';
function normalizeGitHubOwnerRepo(owner, repo) {
function normalizeGitOwnerRepo(owner, repo) {
const normalizedOwner = String(owner || '').trim();
const normalizedRepo = String(repo || '').trim().replace(/\.git$/i, '');
if (!normalizedOwner || !normalizedRepo) {
@@ -9,50 +10,51 @@ function normalizeGitHubOwnerRepo(owner, repo) {
return { owner: normalizedOwner, repo: normalizedRepo };
}
export function parseSkillRepoSource(input, options = {}) {
const raw = typeof input === 'string' ? input.trim() : '';
if (!raw) {
return { ok: false, error: { kind: 'invalidSource', message: 'Repository source is required' } };
}
const explicitSubpath = typeof options.subpath === 'string' && options.subpath.trim() ? options.subpath.trim() : null;
const urlFormat = raw.startsWith('https://') ? 'https' : raw.startsWith('git@') ? 'ssh' : 'shorthand';
const gitHost = urlFormat === 'https' ? raw.split('/')[2] : urlFormat === 'ssh' ? raw.split('@')[1].split(':')[0] : null;
// SSH URL: git@github.com:owner/repo(.git)
const sshMatch = raw.match(/^git@github\.com:([^/\s]+)\/([^\s#]+)$/i);
if (sshMatch) {
const parsed = normalizeGitHubOwnerRepo(sshMatch[1], sshMatch[2]);
if (!parsed) {
return { ok: false, error: { kind: 'invalidSource', message: 'Invalid SSH repository URL' } };
}
return {
ok: true,
host: GITHUB_HOST,
owner: parsed.owner,
repo: parsed.repo,
cloneUrlSsh: `git@github.com:${parsed.owner}/${parsed.repo}.git`,
cloneUrlHttps: `https://github.com/${parsed.owner}/${parsed.repo}.git`,
// For SSH URLs, subpath is only accepted via options.subpath
effectiveSubpath: explicitSubpath,
normalizedRepo: `${parsed.owner}/${parsed.repo}`,
};
if (gitHost === null && urlFormat !== 'shorthand') {
return { ok: false, error: { kind: 'invalidSource', message: 'Invalid repository URL format' } };
}
// HTTPS URL: https://github.com/owner/repo(.git)
const httpsMatch = raw.match(/^https?:\/\/github\.com\/([^/\s]+)\/([^\s#]+)$/i);
if (httpsMatch) {
const parsed = normalizeGitHubOwnerRepo(httpsMatch[1], httpsMatch[2]);
const pathSegments = urlFormat === 'https'
? raw.split('/').slice(3).filter(Boolean)
: urlFormat === 'ssh'
? (raw.split('@')[1].split(':')[1] ?? '').split('/').filter(Boolean)
: null;
const repoName = pathSegments && pathSegments.length > 0
? pathSegments[pathSegments.length - 1].replace(/\.git$/i, '')
: null;
const gitOwner = pathSegments && pathSegments.length > 1
? pathSegments.slice(0, -1).join('/')
: (pathSegments && pathSegments.length === 1 ? pathSegments[0] : null);
// SSH git@host:owner/repo(.git) or HTTPS https://host/owner/repo(.git)
if (urlFormat === 'ssh' || urlFormat === 'https') {
const parsed = normalizeGitOwnerRepo(gitOwner, repoName);
if (!parsed) {
return { ok: false, error: { kind: 'invalidSource', message: 'Invalid HTTPS repository URL' } };
return { ok: false, error: { kind: 'invalidSource', message: `Invalid ${urlFormat} repository URL` } };
}
return {
ok: true,
host: GITHUB_HOST,
host: gitHost,
owner: parsed.owner,
repo: parsed.repo,
cloneUrlSsh: `git@github.com:${parsed.owner}/${parsed.repo}.git`,
cloneUrlHttps: `https://github.com/${parsed.owner}/${parsed.repo}.git`,
cloneUrlSsh: `git@${gitHost}:${parsed.owner}/${parsed.repo}.git`,
cloneUrlHttps: `https://${gitHost}/${parsed.owner}/${parsed.repo}.git`,
// For SSH URLs, subpath is only accepted via options.subpath
effectiveSubpath: explicitSubpath,
normalizedRepo: `${parsed.owner}/${parsed.repo}`,
};
@@ -61,7 +63,7 @@ export function parseSkillRepoSource(input, options = {}) {
// Shorthand: owner/repo[/subpath...]
const shorthandMatch = raw.match(/^([^/\s]+)\/([^/\s]+)(?:\/(.+))?$/);
if (shorthandMatch) {
const parsed = normalizeGitHubOwnerRepo(shorthandMatch[1], shorthandMatch[2]);
const parsed = normalizeGitOwnerRepo(shorthandMatch[1], shorthandMatch[2]);
if (!parsed) {
return { ok: false, error: { kind: 'invalidSource', message: 'Invalid repository source' } };
}