repomix
Version:
A tool to pack repository contents to single file for AI consumption
134 lines (133 loc) • 4.52 kB
JavaScript
import gitUrlParse, {} from 'git-url-parse';
import { RepomixError } from '../../shared/errorHandle.js';
import { logger } from '../../shared/logger.js';
const VALID_NAME_PATTERN = '[a-zA-Z0-9](?:[a-zA-Z0-9._-]*[a-zA-Z0-9])?';
const validShorthandRegex = new RegExp(`^${VALID_NAME_PATTERN}/${VALID_NAME_PATTERN}$`);
export const isValidShorthand = (remoteValue) => {
return validShorthandRegex.test(remoteValue);
};
const isAzureDevOpsUrl = (remoteValue) => {
if (remoteValue.startsWith('git@ssh.dev.azure.com:')) {
return true;
}
try {
const url = new URL(remoteValue);
const hostname = url.hostname.toLowerCase();
if (hostname === 'dev.azure.com' || hostname === 'ssh.dev.azure.com') {
return true;
}
if (hostname.endsWith('.visualstudio.com')) {
return true;
}
return false;
}
catch {
return false;
}
};
export const parseRemoteValue = (remoteValue, refs = []) => {
if (isValidShorthand(remoteValue)) {
logger.trace(`Formatting GitHub shorthand: ${remoteValue}`);
return {
repoUrl: `https://github.com/${remoteValue}.git`,
remoteBranch: undefined,
};
}
if (isAzureDevOpsUrl(remoteValue)) {
return {
repoUrl: remoteValue,
remoteBranch: undefined,
};
}
try {
const parsedFields = gitUrlParse(remoteValue, refs);
parsedFields.git_suffix = true;
const ownerSlashRepo = parsedFields.full_name.split('/').length > 1 ? parsedFields.full_name.split('/').slice(-2).join('/') : '';
if (ownerSlashRepo !== '' && !isValidShorthand(ownerSlashRepo)) {
throw new RepomixError('Invalid owner/repo in repo URL');
}
const repoUrl = parsedFields.toString(parsedFields.protocol);
if (parsedFields.ref) {
return {
repoUrl: repoUrl,
remoteBranch: parsedFields.ref,
};
}
if (parsedFields.commit) {
return {
repoUrl: repoUrl,
remoteBranch: parsedFields.commit,
};
}
return {
repoUrl: repoUrl,
remoteBranch: undefined,
};
}
catch {
throw new RepomixError('Invalid remote repository URL or repository shorthand (owner/repo)');
}
};
export { isExplicitRemoteUrl } from './gitRemoteUrl.js';
export const isValidRemoteValue = (remoteValue, refs = []) => {
try {
parseRemoteValue(remoteValue, refs);
return true;
}
catch {
return false;
}
};
export const parseGitHubRepoInfo = (remoteValue) => {
try {
if (isValidShorthand(remoteValue)) {
const [owner, repo] = remoteValue.split('/');
return { owner, repo };
}
try {
const url = new URL(remoteValue);
const allowedHosts = ['github.com', 'www.github.com'];
if (allowedHosts.includes(url.hostname)) {
const pathParts = url.pathname.split('/').filter(Boolean);
if (pathParts.length >= 2) {
const owner = pathParts[0];
const repo = pathParts[1].replace(/\.git$/, '');
const result = { owner, repo };
if (pathParts.length >= 4 && (pathParts[2] === 'tree' || pathParts[2] === 'commit')) {
result.ref = pathParts.slice(3).join('/');
}
return result;
}
}
}
catch (urlError) {
logger.trace('URL parsing failed, falling back to git-url-parse:', urlError.message);
}
const parsed = gitUrlParse(remoteValue);
if (parsed.source !== 'github.com') {
return null;
}
const [owner, repo] = parsed.full_name.split('/');
if (!owner || !repo) {
return null;
}
const result = {
owner,
repo: repo.replace(/\.git$/, ''),
};
if (parsed.ref) {
result.ref = parsed.ref;
}
else if (parsed.commit) {
result.ref = parsed.commit;
}
return result;
}
catch (error) {
logger.trace('Failed to parse GitHub repo info:', error.message);
return null;
}
};
export const isGitHubRepository = (remoteValue) => {
return parseGitHubRepoInfo(remoteValue) !== null;
};