feat: support GitHub Enterprise URLs for skill and company imports

This commit is contained in:
statxc 2026-04-01 20:42:48 +00:00
parent 6c2c63e0f1
commit 9e1ee925cd
6 changed files with 116 additions and 54 deletions

View file

@ -766,7 +766,14 @@ export function isHttpUrl(input: string): boolean {
}
export function isGithubUrl(input: string): boolean {
return /^https?:\/\/github\.com\//i.test(input.trim());
try {
const url = new URL(input.trim());
if (url.protocol !== "https:" && url.protocol !== "http:") return false;
const segments = url.pathname.split("/").filter(Boolean);
return segments.length >= 2;
} catch {
return false;
}
}
function isGithubSegment(input: string): boolean {
@ -797,13 +804,15 @@ function normalizeGithubImportPath(input: string | null | undefined): string | n
}
function buildGithubImportUrl(input: {
hostname?: string;
owner: string;
repo: string;
ref?: string | null;
path?: string | null;
companyPath?: string | null;
}): string {
const url = new URL(`https://github.com/${input.owner}/${input.repo.replace(/\.git$/i, "")}`);
const host = input.hostname || "github.com";
const url = new URL(`https://${host}/${input.owner}/${input.repo.replace(/\.git$/i, "")}`);
const ref = input.ref?.trim();
if (ref) {
url.searchParams.set("ref", ref);
@ -835,13 +844,14 @@ export function normalizeGithubImportSource(input: string, refOverride?: string)
}
if (!isGithubUrl(trimmed)) {
throw new Error("GitHub source must be a github.com URL or owner/repo[/path] shorthand.");
throw new Error("GitHub source must be a GitHub or GitHub Enterprise URL, or owner/repo[/path] shorthand.");
}
if (!ref) {
return trimmed;
}
const url = new URL(trimmed);
const hostname = url.hostname;
const parts = url.pathname.split("/").filter(Boolean);
if (parts.length < 2) {
throw new Error("Invalid GitHub URL.");
@ -852,18 +862,18 @@ export function normalizeGithubImportSource(input: string, refOverride?: string)
const existingPath = normalizeGithubImportPath(url.searchParams.get("path"));
const existingCompanyPath = normalizeGithubImportPath(url.searchParams.get("companyPath"));
if (existingCompanyPath) {
return buildGithubImportUrl({ owner, repo, ref, companyPath: existingCompanyPath });
return buildGithubImportUrl({ hostname, owner, repo, ref, companyPath: existingCompanyPath });
}
if (existingPath) {
return buildGithubImportUrl({ owner, repo, ref, path: existingPath });
return buildGithubImportUrl({ hostname, owner, repo, ref, path: existingPath });
}
if (parts[2] === "tree") {
return buildGithubImportUrl({ owner, repo, ref, path: parts.slice(4).join("/") });
return buildGithubImportUrl({ hostname, owner, repo, ref, path: parts.slice(4).join("/") });
}
if (parts[2] === "blob") {
return buildGithubImportUrl({ owner, repo, ref, companyPath: parts.slice(4).join("/") });
return buildGithubImportUrl({ hostname, owner, repo, ref, companyPath: parts.slice(4).join("/") });
}
return buildGithubImportUrl({ owner, repo, ref });
return buildGithubImportUrl({ hostname, owner, repo, ref });
}
async function pathExists(inputPath: string): Promise<boolean> {
@ -1214,7 +1224,7 @@ export function registerCompanyCommands(program: Command): void {
if (!isGithubUrl(from) && !isGithubShorthand(from)) {
throw new Error(
"Only GitHub URLs and local paths are supported for import. " +
"Generic HTTP URLs are not supported. Use a GitHub URL (https://github.com/...) or a local directory path.",
"Generic HTTP URLs are not supported. Use a GitHub or GitHub Enterprise URL (https://github.com/... or https://ghe.example.com/...) or a local directory path.",
);
}
sourcePayload = { type: "github", url: normalizeGithubImportSource(from, opts.ref) };