diff --git a/packages/myst-frontmatter/src/socials/validators.spec.ts b/packages/myst-frontmatter/src/socials/validators.spec.ts index c02cdd58a4..da2b8e29bb 100644 --- a/packages/myst-frontmatter/src/socials/validators.spec.ts +++ b/packages/myst-frontmatter/src/socials/validators.spec.ts @@ -226,11 +226,21 @@ describe('validateGitHub', () => { expect(result).toBe('https://github.com/orgs/org'); }); + it('should validate a valid GitHub Enterprise org URL', ({ opts }) => { + const result = validateGitHub('https://github.enterprise.com/orgs/myorg', opts); + expect(result).toBe('https://github.enterprise.com/orgs/myorg'); + }); + + it('should validate a GitHub Enterprise URL with trailing slash', ({ opts }) => { + const result = validateGitHub('https://gh.enterprise.com/team/project/', opts); + expect(result).toBe('https://gh.enterprise.com/team/project/'); + }); + it('should return an error for an invalid GitHub username', ({ opts }) => { const result = validateGitHub('@asdfg#', opts); expect(result).toBeUndefined(); expect(opts.messages.errors?.at(0)?.message).toContain( - `GitHub social identity must be a valid username, org/repo, or org URL`, + `GitHub social identity must be a valid username, org/repo, GitHub URL, or GitHub Enterprise URL`, ); }); @@ -238,7 +248,7 @@ describe('validateGitHub', () => { const result = validateGitHub('https://github.com/user', opts); expect(result).toBeUndefined(); expect(opts.messages.errors?.at(0)?.message).toContain( - `GitHub social identity must be a valid username, org/repo, or org URL`, + `GitHub social identity must be a valid username, org/repo, GitHub URL, or GitHub Enterprise URL`, ); }); @@ -246,7 +256,7 @@ describe('validateGitHub', () => { const result = validateGitHub('https:/example.com', opts); expect(result).toBeUndefined(); expect(opts.messages.errors?.at(0)?.message).toContain( - `GitHub social identity must be a valid username, org/repo, or org URL`, + `GitHub social identity must be a valid username, org/repo, GitHub URL, or GitHub Enterprise URL`, ); }); }); diff --git a/packages/myst-frontmatter/src/socials/validators.ts b/packages/myst-frontmatter/src/socials/validators.ts index e0455b2551..2fd8ac98d4 100644 --- a/packages/myst-frontmatter/src/socials/validators.ts +++ b/packages/myst-frontmatter/src/socials/validators.ts @@ -27,6 +27,8 @@ const TWITTER_URL_REGEX = /^https:\/\/(?:twitter\.com|x\.com)\/@?([a-zA-Z0-9_]{4 // Match a basic identifier (letters, numbers, underscores, full-stops) const GITHUB_USERNAME_REGEX = /^@?([a-zA-Z0-9_.-]+)$/; const GITHUB_ORG_URL_REGEX = /^https:\/\/github\.com\/orgs\/[a-zA-Z0-9_.-]+$/; +// Match GitHub Enterprise URLs with custom domains +const GITHUB_ENTERPRISE_URL_REGEX = /^https:\/\/gh\.[^./]+\.com\/([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)\/?$/; // Match a basic identifier (letters, numbers, underscores, between 4 and 15 characters) const TELEGRAM_REGEX = /^@?([a-zA-Z0-9_]{5,})$/; const TELEGRAM_URL_REGEX = /^https:\/\/(?:t\.me|telegram\.me)\/?([a-zA-Z0-9_]{5,})$/; @@ -155,23 +157,30 @@ export function validateYouTube(input: any, opts: ValidationOptions) { } /** - * Validate value is valid GitHub URL, + * Validate value is valid GitHub URL, including GitHub Enterprise domains */ export function validateGitHub(input: any, opts: ValidationOptions) { const value = validateString(input, opts); if (value === undefined) return undefined; let match: ReturnType; - // URL - if ((match = value.match(GITHUB_USERNAME_REGEX))) { - return match[1]; - } else if ((match = value.match(GITHUB_USERNAME_REPO_REGEX))) { + + // Check for full URLs first (both github.com and GitHub Enterprise) + if ((match = value.match(GITHUB_ENTERPRISE_URL_REGEX))) { + // For GitHub Enterprise URLs, return the full URL return match[0]; } else if ((match = value.match(GITHUB_ORG_URL_REGEX))) { + // Standard GitHub.com org URL return match[0]; + } else if ((match = value.match(GITHUB_USERNAME_REPO_REGEX))) { + // org/repo format (without domain) + return match[0]; + } else if ((match = value.match(GITHUB_USERNAME_REGEX))) { + // username only + return match[1]; } else { return validationError( - `GitHub social identity must be a valid username, org/repo, or org URL: ${value}`, + `GitHub social identity must be a valid username, org/repo, GitHub URL, or GitHub Enterprise URL: ${value}`, opts, ); } diff --git a/packages/myst-frontmatter/src/utils/validators.ts b/packages/myst-frontmatter/src/utils/validators.ts index 9d56ad496a..b9a4ac4fa6 100644 --- a/packages/myst-frontmatter/src/utils/validators.ts +++ b/packages/myst-frontmatter/src/utils/validators.ts @@ -47,6 +47,13 @@ export function validateGithubUrl(value: any, opts: ValidationOptions) { github = `https://github.com/${repo}`; } } + + // For GitHub Enterprise URLs (gh.something.com), just validate as a URL without domain restriction + if (typeof github === 'string' && github.startsWith('https://gh.') && github.includes('.com/')) { + return validateUrl(github, incrementOptions('github', opts)); + } + + // For standard GitHub URLs, keep the existing validation return validateUrl(github, { ...incrementOptions('github', opts), includes: 'github',