76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import { parseGitHubIssueOrPRLink } from './github-links'
|
|
import { parseLinearIssueInput } from './linear-links'
|
|
import type { WorkspaceSourceProvider } from './new-workspace/workspace-source'
|
|
|
|
// Why: narrows the canonical provider union instead of minting a parallel one,
|
|
// so adding Jira here is a one-entry change rather than a new axis.
|
|
export const ISSUE_LINK_PROVIDERS = [
|
|
'github',
|
|
'linear'
|
|
] as const satisfies readonly WorkspaceSourceProvider[]
|
|
|
|
export type IssueLinkProvider = (typeof ISSUE_LINK_PROVIDERS)[number]
|
|
|
|
export function isIssueLinkProvider(value: unknown): value is IssueLinkProvider {
|
|
return ISSUE_LINK_PROVIDERS.includes(value as IssueLinkProvider)
|
|
}
|
|
|
|
/** URL input only. Linear and Jira issue keys are byte-identical in shape, so a
|
|
* bare `STA-335` can never decide a provider — it would override the chip. */
|
|
export function getIssueLinkProviderFromUrl(input: string): IssueLinkProvider | null {
|
|
const trimmed = input.trim()
|
|
if (!/^https?:\/\//i.test(trimmed)) {
|
|
return null
|
|
}
|
|
|
|
// Why: a URL is decisive only if it names that provider's *issue*. A GitHub
|
|
// pull URL is a valid GitHub link but not an issue, and must not flip the
|
|
// provider into a state where the field then refuses to save it.
|
|
if (parseGitHubIssueOrPRLink(trimmed)?.type === 'issue') {
|
|
return 'github'
|
|
}
|
|
if (parseLinearIssueInput(trimmed)) {
|
|
return 'linear'
|
|
}
|
|
return null
|
|
}
|
|
|
|
export type ParsedIssueLinkInput =
|
|
| { provider: 'github'; number: number }
|
|
| { provider: 'linear'; identifier: string; organizationUrlKey?: string }
|
|
|
|
/**
|
|
* Single parse shared by the dialog's save gate and its payload builder, so a
|
|
* value that enables Save is always the value that gets written.
|
|
*/
|
|
export function parseIssueLinkInput(
|
|
input: string,
|
|
provider: IssueLinkProvider
|
|
): ParsedIssueLinkInput | null {
|
|
const trimmed = input.trim()
|
|
if (!trimmed) {
|
|
return null
|
|
}
|
|
|
|
if (provider === 'linear') {
|
|
const parsed = parseLinearIssueInput(trimmed)
|
|
return parsed ? { provider: 'linear', ...parsed } : null
|
|
}
|
|
|
|
const link = parseGitHubIssueOrPRLink(trimmed)
|
|
if (link) {
|
|
// Why: issue and PR numbers live in separate GitHub namespaces for refs; a
|
|
// pull URL pasted here must not silently become an issue link.
|
|
return link.type === 'issue' ? { provider: 'github', number: link.number } : null
|
|
}
|
|
|
|
const numeric = trimmed.startsWith('#') ? trimmed.slice(1) : trimmed
|
|
if (!/^\d+$/.test(numeric)) {
|
|
return null
|
|
}
|
|
const parsed = Number.parseInt(numeric, 10)
|
|
// Why: `/^\d+$/` happily accepts 400 digits, which parseInt turns into
|
|
// Infinity — Save would enable and JSON.stringify would persist `null`.
|
|
return Number.isSafeInteger(parsed) && parsed > 0 ? { provider: 'github', number: parsed } : null
|
|
}
|