diff --git a/README.md b/README.md index c6fd708..1e84da2 100644 --- a/README.md +++ b/README.md @@ -154,13 +154,15 @@ linear-release update --stage="in review" --description="Highlights and rollout ### Environment Variables -| Variable | Required | Description | -| --------------------- | -------- | ---------------------------------------------------------------- | -| `LINEAR_ACCESS_KEY` | Yes | Pipeline access key from Linear | -| `LINEAR_VCS_PROVIDER` | No | Override VCS provider detection: `github`, `gitlab`, `bitbucket` | +| Variable | Required | Description | +| --------------------- | -------- | -------------------------------------------------------------------------- | +| `LINEAR_ACCESS_KEY` | Yes | Pipeline access key from Linear | +| `LINEAR_VCS_PROVIDER` | No | Override VCS provider detection: `github`, `gitlab`, `bitbucket`, `origin` | The provider is detected from the remote hostname, or on GitLab CI from the job environment. Set `LINEAR_VCS_PROVIDER` when neither identifies the host (e.g. self-hosted GitHub Enterprise on a custom domain). When the provider can't be determined, `sync` exits with code `2` before syncing anything. +Cursor Origin remotes (`origin.cursor.com`) use the `origin` provider. Release commit links point to `cursor.com/codebase`. PR references resolve against PRs already imported by Linear's Origin integration; direct issue identifiers work without a PR import. For an Origin mirror whose PRs are hosted on GitHub, set `LINEAR_VCS_PROVIDER=github`. + ### CLI Options | Option | Commands | Description | @@ -332,7 +334,7 @@ linear-release sync --document "Deploy log=Deployed to production at $(date -u + 1. **Fetches the latest release** from your Linear pipeline to determine the commit range 2. **Scans commits** between the commit from the last release and the current commit 3. **Extracts issue identifiers** from branch names and commit messages (e.g., `feat/ENG-123-add-feature`) -4. **Detects pull/merge request numbers** from commit messages — GitHub `Title (#42)` / `Merge pull request #42`, and GitLab `See merge request /!42` trailers (emitted whenever a merge commit is created) +4. **Detects pull/merge request numbers** from commit messages — GitHub and Origin `Title (#42)` / `Merge pull request #42`, and GitLab `See merge request /!42` trailers (emitted whenever a merge commit is created) 5. **Syncs data to Linear** that adds issues and provided links to a newly created completed release (continuous pipelines) or the currently in-progress release (scheduled pipelines). PR/MR numbers are sent alongside the repository info, and Linear resolves them back to any issues linked to those PRs/MRs — so issues attached only via a PR/MR (not mentioned in a commit message or branch name) are still picked up. > [!NOTE] diff --git a/src/index.ts b/src/index.ts index ede3e37..7042084 100644 --- a/src/index.ts +++ b/src/index.ts @@ -89,7 +89,7 @@ Options: Environment: LINEAR_ACCESS_KEY Pipeline access key (required) - LINEAR_VCS_PROVIDER Override VCS provider detection: github|gitlab|bitbucket + LINEAR_VCS_PROVIDER Override VCS provider detection: github|gitlab|bitbucket|origin Examples: linear-release sync diff --git a/src/provider.test.ts b/src/provider.test.ts index 253fa41..2ce8ae8 100644 --- a/src/provider.test.ts +++ b/src/provider.test.ts @@ -111,6 +111,40 @@ describe("resolveRepoInfo", () => { expect(resolveRepoInfo(detected, { LINEAR_VCS_PROVIDER: "GitLab" })?.provider).toBe("gitlab"); }); + it.each([ + "https://origin.cursor.com/acme/repo.git", + "https://token@origin.cursor.com/acme/repo.git", + "git@origin.cursor.com:acme/repo.git", + "https://ORIGIN.CURSOR.COM:443/acme/repo.git", + ])("resolves Origin coordinates and web URL from %s", (url) => { + expect(resolveRepoInfo(parseRepoUrl(url), {})).toMatchObject({ + provider: "origin", + owner: "acme", + name: "repo", + url: "https://cursor.com/codebase/acme/repo", + }); + }); + + it.each(["https://origin.cursor.com.example.org/acme/repo.git", "https://cursor.com/acme/repo.git"])( + "does not infer Origin from %s", + (url) => { + expect(captureError(() => resolveRepoInfo(parseRepoUrl(url), {})).code).toBe("unknown-provider"); + }, + ); + + it("accepts an Origin override", () => { + expect( + resolveRepoInfo(parseRepoUrl("https://git.example.com/acme/repo.git"), { LINEAR_VCS_PROVIDER: "Origin" }), + ).toMatchObject({ provider: "origin", url: "https://cursor.com/codebase/acme/repo" }); + }); + + it("allows mirrored repositories to keep using their GitHub provider override", () => { + expect( + resolveRepoInfo(parseRepoUrl("https://origin.cursor.com/acme/repo.git"), { LINEAR_VCS_PROVIDER: "github" }) + ?.provider, + ).toBe("github"); + }); + it("treats an empty override as unset", () => { const detected = parseRepoUrl("https://github.com/acme/repo.git"); expect(resolveRepoInfo(detected, { LINEAR_VCS_PROVIDER: "" })?.provider).toBe("github"); diff --git a/src/provider.ts b/src/provider.ts index a5e3c88..e9700b0 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -57,6 +57,9 @@ function normalizeHost(host: string): string { } function hostToProvider(host: string): RepositoryProvider | null { + if (host === "origin.cursor.com") { + return "origin"; + } if (host === "gitlab.com" || host.includes("gitlab")) { return "gitlab"; } @@ -71,7 +74,9 @@ function hostToProvider(host: string): RepositoryProvider | null { function parseProvider(value: string): RepositoryProvider | null { const provider = value.toLowerCase(); - return provider === "github" || provider === "gitlab" || provider === "bitbucket" ? provider : null; + return provider === "github" || provider === "gitlab" || provider === "bitbucket" || provider === "origin" + ? provider + : null; } function inferProviderFromCI( @@ -98,7 +103,7 @@ function resolveProvider(repoInfo: RepoInfo, env: Record = process.env, ): ResolvedRepoInfo | null { - return repoInfo ? { ...repoInfo, provider: resolveProvider(repoInfo, env) } : null; + if (!repoInfo) { + return null; + } + const provider = resolveProvider(repoInfo, env); + // Origin's git transport host differs from its web UI, where release commit links point. + const url = + provider === "origin" && repoInfo.owner && repoInfo.name + ? `https://cursor.com/codebase/${repoInfo.owner}/${repoInfo.name}` + : repoInfo.url; + return { ...repoInfo, provider, url }; } diff --git a/src/types.ts b/src/types.ts index ba43dd0..4bb9eb9 100644 --- a/src/types.ts +++ b/src/types.ts @@ -77,7 +77,7 @@ export type GitInfo = { message: string | null; }; -export type RepositoryProvider = "github" | "gitlab" | "bitbucket"; +export type RepositoryProvider = "github" | "gitlab" | "bitbucket" | "origin"; export type RepoInfo = { owner: string | null;