Files
orca/mobile/src/tasks/github-project-reference.ts
T
Gon SongandNeil 85d1ffc072 fix: accept enterprise managed GitHub owner logins (#20450)
Unify owner validation across project pickers and repository overrides. Preserve EMU usernames in API and auth-status branch-prefix resolution, with regression coverage.

Co-authored-by: Neil <neil@stably.ai>
2026-09-16 14:41:15 -07:00

89 lines
2.4 KiB
TypeScript

import {
GITHUB_OWNER_NUMBER_SHORTHAND_RE,
GITHUB_OWNER_SLUG_RE
} from '../../../src/shared/github/owner-slug'
import type { GitHubProjectIdentity } from '../../../src/shared/github/project-identity'
export type GitHubProjectOwnerType = GitHubProjectIdentity['ownerType']
export type GitHubProjectRef = GitHubProjectIdentity
export type GitHubProjectSettings = {
pinned: GitHubProjectRef[]
recent: Array<GitHubProjectRef & { lastOpenedAt: string }>
lastViewByProject: Record<string, { viewId: string }>
activeProject: GitHubProjectRef | null
}
export type GitHubProjectSummary = GitHubProjectRef & {
id: string
title: string
url: string
source: string
}
export type GitHubProjectPartialFailure = { owner: string; message: string }
export type GitHubProjectViewSummary = {
id: string
number: number
name: string
layout: 'TABLE_LAYOUT' | 'BOARD_LAYOUT' | 'ROADMAP_LAYOUT'
}
export type ParsedGitHubProjectInput = {
owner: string
number: number
host?: string
viewNumber?: number
}
const OWNER_RE = GITHUB_OWNER_SLUG_RE
function positiveInteger(value: string | undefined): number | null {
if (!value || !/^\d+$/.test(value)) {
return null
}
const parsed = Number(value)
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : null
}
export function parseGitHubProjectInput(input: string): ParsedGitHubProjectInput | null {
const trimmed = input.trim()
const short = GITHUB_OWNER_NUMBER_SHORTHAND_RE.exec(trimmed)
if (short) {
const number = positiveInteger(short[2])
return number ? { owner: short[1]!, number } : null
}
try {
const url = new URL(trimmed)
if (
(url.protocol !== 'https:' && url.protocol !== 'http:') ||
url.username ||
url.password ||
!url.host
) {
return null
}
const parts = url.pathname.split('/').filter(Boolean)
const hasView = parts.length === 6 && parts[4] === 'views'
if (
(parts[0] !== 'orgs' && parts[0] !== 'users') ||
!OWNER_RE.test(parts[1] ?? '') ||
parts[2] !== 'projects' ||
(parts.length !== 4 && !hasView)
) {
return null
}
const number = positiveInteger(parts[3])
const viewNumber = hasView ? positiveInteger(parts[5]) : null
if (!number || (hasView && !viewNumber)) {
return null
}
return {
owner: parts[1]!,
number,
host: url.host.toLowerCase(),
...(viewNumber ? { viewNumber } : {})
}
} catch {
return null
}
}