import { githubOpening } from './github' // Opening a destination by its URL (integrations §3): each adapter that can // be opened so declares its URL syntax, canonical URL, refs and names. The // core tries every declaration and never names a system, a kind of // destination or a ref layout of its own. Pure, for both processes. /** A destination by its system, host and canonical locator (integrations §3). */ export interface DestinationName { system: string host: string locator: string } /** A ref at the repository's remote, and the remote-tracking ref it is fetched to (below `refs/remotes//`). */ export interface DeclaredRef { ref: string tracking: string } export interface UrlOpening { system: string names: { /** The system's display name: `GitHub`. */ system: string /** What a destination is called: `pull request`. */ noun: string /** The short kind for a tab: `pr`. */ kind: string /** A URL of the syntax, for placeholders. */ example: string } /** The short form of a locator: `#1287`. */ short(locator: string): string /** The destination a typed URL names, with anything after it ignored; null when the URL isn't of this syntax. */ parseUrl(text: string): { host: string; locator: string } | null /** The destination's canonical URL, without a suffix. */ canonicalUrl(host: string, locator: string): string /** Where to clone the destination's repository when no open one has it. */ cloneUrl(host: string, locator: string): string /** Whether a remote's URL is the destination's repository. */ isRepository(host: string, locator: string, remoteUrl: string): boolean refs: { head(locator: string): DeclaredRef base(branch: string): DeclaredRef /** The ref at the remote a head's remote-tracking ref came from; null when it isn't one of this system's. */ headOf(tracking: string): string | null } /** The destination a head's remote-tracking ref names, given the URL of the remote it is under. */ destinationOf(remoteUrl: string, tracking: string): { host: string; locator: string } | null } /** The declarations of the adapters the app registers. */ export const URL_OPENINGS: readonly UrlOpening[] = [githubOpening] export const openingOf = (system: string, openings: readonly UrlOpening[] = URL_OPENINGS): UrlOpening | undefined => openings.find((o) => o.system === system) /** The destination a URL names, whichever system's syntax it is of. */ export function parseDestinationUrl(text: string, openings: readonly UrlOpening[] = URL_OPENINGS): DestinationName | null { for (const o of openings) { const d = o.parseUrl(String(text ?? '')) if (d) return { system: o.system, ...d } } return null } /** * A destination's change key (format §11.2, integrations §3.1): its canonical * URL, lower-cased, whatever form of it was typed or which remote URL named * the repository. */ export function destinationKey(d: DestinationName, openings: readonly UrlOpening[] = URL_OPENINGS): string | undefined { return openingOf(d.system, openings)?.canonicalUrl(d.host, d.locator).toLowerCase() } const REMOTE = /^\w[\w.-]*$/ /** * A subject opened from a destination: its labels (`/`, * `merge-base(/)`) → the remote, the head's remote-tracking * ref and the ref it came from, and the base branch. Null for any other * subject. */ export function openedSubject( s: { headLabel?: string; baseLabel?: string } | null | undefined, openings: readonly UrlOpening[] = URL_OPENINGS ): { system: string; remote: string; tracking: string; head: string; base: string | null } | null { const label = s?.headLabel ?? '' const slash = label.indexOf('/') const remote = label.slice(0, slash) if (slash < 0 || !REMOTE.test(remote)) return null const tracking = label.slice(slash + 1) for (const o of openings) { const head = o.refs.headOf(tracking) if (!head) continue const b = /^merge-base\(([^)]+)\)$/.exec(s?.baseLabel ?? '')?.[1] const base = b?.startsWith(`${remote}/`) ? b.slice(remote.length + 1) : null return { system: o.system, remote, tracking, head, base } } return null } /** The destination a subject was opened from, given its repository's remotes; undefined for any other subject. */ export function openedDestination( input: { remotes: { remote: string; url: string }[]; subject: { headLabel?: string; baseLabel?: string } }, openings: readonly UrlOpening[] = URL_OPENINGS ): DestinationName | undefined { const o = openedSubject(input.subject, openings) const url = o && input.remotes.find((r) => r.remote === o.remote)?.url const d = url ? openingOf(o.system, openings)?.destinationOf(url, o.tracking) : null return d ? { system: o!.system, ...d } : undefined } /** The change key of a subject opened from a destination (format §11.2): the destination's key; undefined for any other subject. */ export function openedDestinationKey( input: { remotes: { remote: string; url: string }[]; subject: { headLabel?: string; baseLabel?: string } }, openings: readonly UrlOpening[] = URL_OPENINGS ): string | undefined { const d = openedDestination(input, openings) return d && destinationKey(d, openings) } /** What opening a destination by its URL found. */ export interface OpenedDestination extends DestinationName { /** The destination's title, when the system could be asked. */ title?: string baseRef: string /** Where its commits were fetched from: an open repository's remote, or a new clone's. */ remote: string /** Why the system wasn't asked (no token): the base is then the default branch. */ problem?: string }