Files
orca/src/main/gitlab/project-ref-parser.ts
T
JinjingandOrca f72c22532a Support GitLab MR unlinking and AI generation in ChecksPanel (#5204)
* feat: support GitLab MR unlinking and AI generation in ChecksPanel

Integrate GitLab merge request actions alongside GitHub pull requests in
the sidebar checks panel. This includes unlinking GitLab MRs, enabling
AI-driven title and body generation for GitLab, and dynamically adapting
menu labels (e.g. "More MR actions" vs "More PR actions") depending on
the active provider.

* fix: handle null base ref in hosted review creation and add tests

Co-authored-by: Orca <help@stably.ai>

* Extract sub-components and hooks from renderer components

To improve component focus and maintainability, extract large inline
sub-components, custom hooks, and logic helpers into dedicated files:

- Extract `HeroPaired` from `MobileHero` to `MobileHeroPairedDevices`.
- Move `ChromePreview` from `ThemeStep` to `theme-chrome-preview`.
- Refactor `HostedReviewActions` to use `useHostedReviewActions` hook.
- Move MCP config loading from `McpConfigSection` to helper file.

* Refactor usage panes to extract shared formatters and tables

Extract duplicated formatting utilities to a shared helper module. Move
the large, inline recent sessions tables into dedicated sub-components to
reduce duplication and simplify the parent pane components.

* Support self-hosted GitLab instances for MR creation eligibility

* Extract and check the remote host against `glab auth status` to
  dynamically recognize and authenticate self-hosted GitLab instances
  without requiring them to be in a hardcoded list.
* Refactor stats usage panes by extracting reusable breakdown sections
  and sessions tables to eliminate duplication.
* Suggest Linear prompts only if the launcher can resolve the CLI.

* Extract GitLab project ref tests and update usage stats translations

- Move GitLab project ref parsing tests into a dedicated test file to
  keep modules focused and add tests for candidate parsing.
- Add missing translations for the usage sessions table and breakdown
  section across multiple locales.

* Optimize GitLab ref parsing, deduplicate formatters, and add locales

- Clean up GitLab ref parsing by extracting normalized known hosts.
- Fix an escaped newline sequence in the self-hosted GitLab mock test.
- Deduplicate stats helper functions into a single shared file.
- Translate path status message strings across ES, JA, KO, and ZH.

---------

Co-authored-by: Orca <help@stably.ai>
2026-06-11 20:18:28 -07:00

85 lines
2.6 KiB
TypeScript

import type { GitLabProjectRef } from '../../shared/types'
export type ProjectRef = GitLabProjectRef
/**
* Hosts always treated as GitLab. Self-hosted instances are added at
* runtime via `getGlabKnownHosts()`, which inspects `glab auth status`.
*/
export const DEFAULT_GITLAB_HOSTS = ['gitlab.com'] as const
export function normalizeGitLabHost(value: string): string {
return value.trim().toLowerCase()
}
function stripGitSuffix(path: string): string {
return path.replace(/\/+$/, '').replace(/\.git$/i, '')
}
function makeProjectRefForTrustedHost(host: string, path: string): ProjectRef | null {
const normalizedHost = normalizeGitLabHost(host)
const normalizedPath = stripGitSuffix(path.replace(/^\/+/, '')).trim()
// Reject paths without at least one group segment — `gitlab.com:foo`
// alone is not a project reference.
if (!normalizedPath.includes('/')) {
return null
}
return { host: normalizedHost, path: normalizedPath }
}
function makeProjectRef(
host: string,
path: string,
knownHosts: readonly string[]
): ProjectRef | null {
const normalizedHost = normalizeGitLabHost(host)
const normalizedKnownHosts = knownHosts.map(normalizeGitLabHost)
if (!normalizedKnownHosts.includes(normalizedHost)) {
return null
}
return makeProjectRefForTrustedHost(normalizedHost, path)
}
export function parseRemoteProjectRefCandidate(remoteUrl: string): ProjectRef | null {
const trimmed = remoteUrl.trim()
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
const scpLike = trimmed.match(/^(?:[^@/:]+@)?([^:\s/]+):([^\s]+?)(?:\.git)?$/)
if (scpLike) {
return makeProjectRefForTrustedHost(scpLike[1], scpLike[2])
}
}
try {
const url = new URL(trimmed)
if (!['http:', 'https:', 'ssh:', 'git:', 'git+ssh:'].includes(url.protocol.toLowerCase())) {
return null
}
return makeProjectRefForTrustedHost(url.hostname, url.pathname)
} catch {
return null
}
}
export function parseGitLabProjectRef(
remoteUrl: string,
knownHosts: readonly string[] = DEFAULT_GITLAB_HOSTS
): ProjectRef | null {
const trimmed = remoteUrl.trim()
if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) {
const scpLike = trimmed.match(/^(?:[^@/:]+@)?([^:\s/]+):([^\s]+?)(?:\.git)?$/)
if (scpLike) {
return makeProjectRef(scpLike[1], scpLike[2], knownHosts)
}
}
try {
const url = new URL(trimmed)
if (!['http:', 'https:', 'ssh:', 'git:', 'git+ssh:'].includes(url.protocol.toLowerCase())) {
return null
}
return makeProjectRef(url.hostname, url.pathname, knownHosts)
} catch {
return null
}
}