Files
outline/plugins/linear/shared/LinearUtils.ts
T
Claude 396ceb34bb feat: Unfurl Linear review (Diffs) urls as pull request mentions
Linear's new Diffs feature exposes pull request reviews at
linear.review/{owner}/{repo}/pull/{number}, mirroring GitHub pull request
urls. Pasting such a url into a document can now be converted to a mention
that renders with pull request state, matching the GitHub plugin.

Linear's public API does not expose pull requests directly yet, so the
unfurl reads the pull request data Linear syncs onto the attachments of
linked issues via the attachmentsForURL query.

https://claude.ai/code/session_01F9cLTRp6WsFHDxGtd5euGW
2026-06-10 16:55:40 +00:00

89 lines
2.2 KiB
TypeScript

import queryString from "query-string";
import env from "@shared/env";
import { integrationSettingsPath } from "@shared/utils/routeHelpers";
export const LinearOAuthNonceCookie = "linearOAuthNonce";
export type OAuthState = {
teamId: string;
nonce: string;
};
export class LinearUtils {
private static oauthScopes = "read,issues:create";
public static tokenUrl = "https://api.linear.app/oauth/token";
public static revokeUrl = "https://api.linear.app/oauth/revoke";
/** Hostname Linear uses for review (Diffs) urls, mirroring GitHub pull request urls. */
public static reviewHost = "linear.review";
private static authBaseUrl = "https://linear.app/oauth/authorize";
private static settingsUrl = integrationSettingsPath("linear");
static parseState(state: string): OAuthState | undefined {
try {
return JSON.parse(state);
} catch {
return undefined;
}
}
static successUrl() {
return this.settingsUrl;
}
static errorUrl(error: string) {
return `${this.settingsUrl}?error=${error}`;
}
static callbackUrl(
{ baseUrl, params }: { baseUrl: string; params?: string } = {
baseUrl: env.URL,
params: undefined,
}
) {
return params
? `${baseUrl}/api/linear.callback?${params}`
: `${baseUrl}/api/linear.callback`;
}
/**
* Returns a color representing the given pull request status.
*
* @param status Pull request status synced from Linear, e.g. "open" or "merged".
* @returns a hex color string.
*/
public static getColorForPullRequestStatus(status: string) {
switch (status) {
case "open":
case "reopened":
case "approved":
return "#238636";
case "inReview":
return "#d29922";
case "merged":
return "#8250df";
case "closed":
return "#f85149";
case "draft":
default:
return "#848d97";
}
}
static authUrl({ state }: { state: OAuthState }) {
const params = {
client_id: env.LINEAR_CLIENT_ID,
redirect_uri: this.callbackUrl(),
state: JSON.stringify(state),
scope: this.oauthScopes,
response_type: "code",
prompt: "consent",
actor: "app",
};
return `${this.authBaseUrl}?${queryString.stringify(params)}`;
}
}