Security News
GitHub Removes Malicious Pull Requests Targeting Open Source Repositories
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
@linear/sdk
Advanced tools
@linear/sdk is an SDK for interacting with the Linear API, which allows developers to programmatically manage issues, projects, teams, and other resources within the Linear app. It provides a convenient way to integrate Linear's functionalities into your own applications.
Authentication
This feature allows you to authenticate with the Linear API using an API key. The LinearClient instance is then used to make further API calls.
const { LinearClient } = require('@linear/sdk');
const client = new LinearClient({ apiKey: 'your-api-key' });
Create Issue
This feature allows you to create a new issue in a specified team. You need to provide the team ID, title, and description of the issue.
const issue = await client.issueCreate({
teamId: 'team-id',
title: 'New Issue',
description: 'Description of the issue'
});
Fetch Issues
This feature allows you to fetch a list of issues. The issues are returned as an array of nodes, which you can then process or display as needed.
const issues = await client.issues();
console.log(issues.nodes);
Update Issue
This feature allows you to update an existing issue. You need to provide the issue ID and the fields you want to update.
const updatedIssue = await client.issueUpdate('issue-id', {
title: 'Updated Title',
description: 'Updated Description'
});
Delete Issue
This feature allows you to delete an existing issue. You need to provide the issue ID, and it returns a success status.
const deleted = await client.issueDelete('issue-id');
console.log(deleted.success);
jira-client is an SDK for interacting with the JIRA API. It provides similar functionalities for managing issues, projects, and other resources within JIRA. Compared to @linear/sdk, jira-client is tailored for JIRA's ecosystem and offers extensive support for JIRA-specific features.
asana is an SDK for interacting with the Asana API. It allows developers to manage tasks, projects, teams, and other resources within Asana. Like @linear/sdk, it provides a convenient way to integrate Asana's functionalities into your applications, but it is specific to the Asana platform.
github is an SDK for interacting with the GitHub API. It allows developers to manage repositories, issues, pull requests, and other resources within GitHub. While it offers similar issue management functionalities, it is tailored for GitHub's ecosystem and includes features specific to version control and collaboration.
Linear helps streamline software projects, sprints, tasks, and
bug tracking. It's built for high-performance teams.
Connect to the Linear API and interact with your data in a few steps:
Install the Linear Client
Using npm:
npm install @linear/sdk graphql graphql-request
Or yarn:
yarn add @linear/sdk graphql graphql-request
The graphql
and graphql-request
packages are required as peer dependencies.
Create a Linear API authentication token
Login or signup to Linear
Go to Settings > Api
Create a Personal API Key
Create a Linear Client
Using the API key created in step 2:
import { LinearClient } from '@linear/sdk'
const client = new LinearClient({
apiKey: YOUR_PERSONAL_API_KEY
})
Query for your issues
Using async await syntax:
async function getMyIssues() {
const me = await linearClient.viewer;
const myIssues = await me?.assignedIssues();
myIssues?.nodes?.map(issue => {
console.log(`${me?.displayName} has issue: ${issue?.title}`);
});
return myIssues;
}
getMyIssues();
Or promises:
linearClient.viewer.then(me => {
return me?.assignedIssues()?.then(myIssues => {
myIssues?.nodes?.map(issue => {
console.log(`${me?.displayName} has issue: ${issue?.title}`);
});
return myIssues;
});
});
The Linear Client exposes the Linear GraphQL schema through strongly typed models and operations.
All operations return models, which can be used to perform operations for other models.
All types are accessible through the Linear Client package. It is written in Typescript:
import { LinearClient, LinearFetch, User } from "@linear/sdk";
const linearClient = new LinearClient({ apiKey });
async function getCurrentUser(): LinearFetch<User> {
return linearClient.viewer;
}
Some models can be fetched from the Linear Client without any arguments:
const me = await linearClient.viewer;
const org = await linearClient.organization;
Other models are exposed as connections, and return a list of nodes:
const issues = await linearClient.issues();
const firstIssue = issues?.nodes?.[0];
All required variables are passed as the first arguments:
const user = await linearClient.user("user-id");
const team = await linearClient.team("team-id");
Any optional variables are passed into the last argument as an object:
const fiftyProjects = await linearClient.projects({ first: 50 });
const allComments = await linearClient.comments({ includeArchived: true });
Most models expose operations to fetch other models:
const me = await linearClient.viewer;
const myIssues = await me?.assignedIssues();
const myFirstIssue = myIssues?.nodes?.[0];
const myFirstIssueComments = await myFirstIssue?.comments();
const myFirstIssueFirstComment = myFirstIssueComments?.nodes?.[0];
const myFirstIssueFirstCommentUser = await myFirstIssueFirstComment?.user;
NOTE: Parenthesis is required only if the operation takes an optional variables object.
To create a model, call the Linear Client mutation and pass in the input object:
const teams = await linearClient.teams();
const team = teams?.nodes?.[0];
if (team?.id) {
await linearClient.issueCreate({ teamId: team.id, title: "My Created Issue" });
}
To update a model, call the Linear Client mutation and pass in the required variables and input object:
const me = await linearClient.viewer;
if (me?.id) {
await linearClient.userUpdate(me.id, { displayName: "My Updated Name" });
}
All mutations are exposed in the same way:
const projects = await linearClient.projects();
const project = projects?.nodes?.[0];
if (project?.id) {
await linearClient.projectArchive(project.id);
}
Mutations will often return a success boolean and the mutated entity:
const commentPayload = await linearClient.commentCreate({ issueId: "some-issue-id" });
if (commentPayload?.success) {
return commentPayload.comment;
} else {
return new Error("Failed to create comment");
}
Connection models have helpers to fetch the next and previous pages of results:
const issues = await linearClient.issues({ after: "some-issue-cursor", first: 10 });
const nextIssues = await issues?.fetchNext();
const prevIssues = await issues?.fetchPrevious();
Pagination info is exposed and can be passed to the query operations. This uses the Relay Connection spec:
const issues = await linearClient.issues();
const hasMoreIssues = issues?.pageInfo?.hasNextPage;
const issuesEndCursor = issues?.pageInfo?.endCursor;
const moreIssues = await linearClient.issues({ after: issuesEndCursor, first: 10 });
Results can be ordered using the orderBy
optional variable:
import { LinearDocument } from "@linear/sdk";
const issues = await linearClient.issues({ orderBy: LinearDocument.PaginationOrderBy.UpdatedAt });
Create a file upload URL, upload the file to external storage, and attach the file by asset URL:
import { Issue, LinearFetch } from "@linear/sdk";
async function createIssueWithFile(title: string, file: File, uploadData: RequestInit): LinearFetch<Issue> {
/** Fetch a storage URL to upload the file to */
const uploadPayload = await linearClient.fileUpload(file.type, file.name, file.size);
/** Upload the file to the storage URL using the authentication header */
const authHeader = uploadPayload?.uploadFile?.headers?.[0];
const uploadUrl = uploadPayload?.uploadFile?.uploadUrl;
if (uploadUrl && authHeader?.key && authHeader?.value) {
await fetch(uploadUrl, {
method: "PUT",
headers: {
[authHeader.key]: authHeader.value,
"cache-control": "max-age=31536000",
},
...uploadData,
});
/** Use the asset URL to attach the stored file */
const assetUrl = uploadPayload?.uploadFile?.assetUrl;
if (assetUrl) {
const issuePayload = await linearClient.issueCreate({
title,
/** Use the asset URL in a markdown link */
description: `Attached file: ![${assetUrl}](${encodeURI(assetUrl)})`,
teamId: "team-id",
});
return issuePayload?.issue;
}
}
return undefined;
}
Errors can be caught and interrogated by wrapping the operation in a try catch block:
async function createComment(input: LinearDocument.CommentCreateInput): LinearFetch<Comment | UserError> {
try {
/** Try to create a comment */
const commentPayload = await linearClient.commentCreate(input);
/** Return it if available */
return commentPayload?.comment;
} catch (error) {
/** The error has been parsed by Linear Client */
throw error;
}
}
Or by catching the error thrown from a calling function:
async function archiveFirstIssue(): LinearFetch<ArchivePayload> {
const me = await linearClient.viewer;
const issues = await me?.assignedIssues();
const firstIssue = issues?.nodes?.[0];
if (firstIssue?.id) {
const payload = await linearClient.issueArchive(firstIssue.id);
return payload;
} else {
return undefined;
}
}
archiveFirstIssue().catch(error => {
throw error;
});
The parsed error type can be compared to determine the course of action:
import { InvalidInputLinearError, LinearError, LinearErrorType } from '@linear/sdk'
import { UserError } from './custom-errors'
const input = { name: "Happy Team" };
createTeam(input).catch(error => {
if (error instanceof InvalidInputLinearError) {
/** If the mutation has failed due to an invalid user input return a custom user error */
return new UserError(input, error);
} else {
/** Otherwise throw the error and handle in the calling function */
throw error;
}
});
Information about the request
resulting in the error is attached if available:
run().catch(error => {
if (error instanceof LinearError) {
console.error("Failed query:", error.query);
console.error("With variables:", error.variables);
}
throw error;
});
Information about the response
is attached if available:
run().catch(error => {
if (error instanceof LinearError) {
console.error("Failed HTTP status:", error.status);
console.error("Failed response data:", error.data);
}
throw error;
});
Any GraphQL errors
are parsed and added to an array:
run().catch(error => {
if (error instanceof LinearError) {
error.errors?.map(graphqlError => {
console.log("Error message", graphqlError.message);
console.log("LinearErrorType of this GraphQL error", graphqlError.type);
console.log("Error due to user input", graphqlError.userError);
console.log("Path through the GraphQL schema", graphqlError.path);
});
}
throw error;
});
The raw
error returned by the graphql-request client is still available:
run().catch(error => {
if (error instanceof LinearError) {
console.log("The original error", error.raw);
}
throw error;
});
This functionality is currently under development and must be performed by the consumer:
The Linear Client is generated from the Linear GraphQL schema.
You can use any GraphQL client to introspect and explore the schema. Such as Insomnia or GraphQL Playground.
Point the GraphQL client to the Linear production API endpoint:
https://api.linear.app/graphql
Linear supports OAuth2 authentication, which is recommended if you're building applications to integrate with Linear.
NOTE: It is highly recommended you create a workspace for the purpose of managing the OAuth2 Application. As each admin user will have access.
Create an OAuth2 application in Linear
Create a new OAuth2 Application
Configure the redirect callback URLs to your application
Redirect user access requests to Linear
When authorizing a user to the Linear API, redirect to an authorization URL with correct parameters and scopes:
GET https://linear.app/oauth/authorize
Parameters
client_id
(required) - Client ID provided when you create the OAuth2 Applicationredirect_uri
(required) - Redirect URIresponse_type=code
(required) - Expected response typescope
(required) - Comma separated list of scopes (listed below)state
(optional) - Prevents CSRF attacks and should always be supplied. Read more about it hereScopes
read
- (Default) Read access for the user's account. This scope will always be present.write
- Write access for the user's account.issues:create
- Special scope to only gain access in order to create new issues. If this is the main reason for your application, you should ask for this scope instead of write
admin
- Full access to admin level endpoints. You should never ask for this permission unless it's absolutely neededExample
GET https://linear.app/oauth/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URL&state=SECURE_RANDOM&scope=read
GET https://linear.app/oauth/authorize?client_id=client1&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Foauth%2Fcallback&response_type=code&scope=read,write
Handle the redirect URLs you specified in the OAuth2 Application
Once the user approves your application they will be redirected back to your application, with the OAuth authorization code
in the URL params.
Any state
parameter you specified in step 2 will also be returned in the URL params and must match the value specified in step 2. If the values do not match, the request should not be trusted.
Example
GET https://example.com/oauth/callback?code=9a5190f637d8b1ad0ca92ab3ec4c0d033ad6c862&state=b1ad0ca92
Exchange code
for an access token
After receiving the code
, you can exchange it for a Linear API access token:
POST https://api.linear.app/oauth/token
Parameters
code
- (required) Authorization code from the previous stepredirect_uri
- (required) Same redirect URI which you used in the previous stepclient_id
- (required) Application's client IDclient_secret
- (required) Application's client secretgrant_type=authorization_code
(required)Response
After a successful request, a valid access token will be returned in the response:
{
"access_token": "00a21d8b0c4e2375114e49c067dfb81eb0d2076f48354714cd5df984d87b67cc",
"token_type": "Bearer",
"expires_in": 315705599,
"scope": [
"read",
"write"
]
}
Make an API request
Initialize the Linear Client with the access token:
const client = new LinearClient({ accessToken: response.access_token })
const me = await client.viewer
Revoke an access token
To revoke a user's access to your application pass the access token as Bearer token in the authorization header (Authorization: Bearer <ACCESS_TOKEN>
) or as the access_token
form field:
POST https://api.linear.app/oauth/revoke
Response
Expected HTTP status:
200
- token was revoked400
- unable to revoke token (e.g. token was already revoked)401
- unable to authenticate with the tokenIf you run into problems, have questions or suggestions:
Both options are available through the user menu in Linear.
The Linear Client wraps the Linear SDK, provides a graphql-request client, and parses errors.
The graphql-request client can be configured by passing the RequestInit
object to the Linear Client constructor:
const linearClient = new LinearClient({ apiKey, headers: { "my-header": "value" } });
The graphql-request client is accessible through the Linear Client:
const graphQLClient = linearClient.client;
graphQLClient.setHeader("my-header", "value");
The Linear GraphQL API can be queried directly by passing a raw GraphQL query to the graphql-request client:
const graphQLClient = linearClient.client;
const cycle = await graphQLClient.rawRequest(
gql`
query cycle($id: String!) {
cycle(id: $id) {
id
name
completedAt
}
}
`,
{ id: "cycle-id" }
);
In order to use a custom GraphQL Client, the Linear SDK must be extended and provided with a request function:
import { LinearError, LinearFetch, LinearRequest, LinearSdk, parseLinearError, UserConnection } from "@linear/sdk";
import { DocumentNode, GraphQLClient, print } from "graphql";
import { CustomGraphqlClient } from "./graphql-client";
/** Create a custom client configured with the Linear API base url and API key */
const customGraphqlClient = new CustomGraphqlClient("https://api.linear.app/graphql", {
headers: { Authorization: apiKey },
});
/** Create the custom request function */
const customLinearRequest: LinearRequest = <Response, Variables>(
document: DocumentNode,
variables?: Variables
) => {
/** The request must take a GraphQL document and variables, then return a promise for the result */
return customGraphqlClient.request<Response>(print(document), variables).catch(error => {
/** Optionally catch and parse errors from the Linear API */
throw parseLinearError(error);
});
};
/** Extend the Linear SDK to provide a request function using the custom client */
class CustomLinearClient extends LinearSdk {
public constructor() {
super(customLinearRequest);
}
}
/** Create an instance of the custom client */
const customLinearClient = new CustomLinearClient();
/** Use the custom client as if it were the Linear Client */
async function getUsers(): LinearFetch<UserConnection> {
const users = await customLinearClient.users();
return users;
}
The Linear Client uses custom GraphQL Code Generator plugins to produce a typed SDK for all operations and models exposed by the Linear production API.
# install dependencies
yarn
# build all packages
yarn build
# test all packages
yarn test
# update the schema from the production API
yarn schema
This monorepo uses yarn workspaces
and lerna
to publish packages independently.
Generated code uses file prefix _generated
and should never be manually updated.
packages
common - Common functions and logging
import - Import tooling for uploading from other systems
sdk - The Linear Client SDK for interacting with the Linear GraphQL API
codegen-doc - GraphQL codegen plugin to generate GraphQL fragments and documents
codegen-sdk - GraphQL codegen plugin to generate Typescript SDK from fragments and documents
codegen-test - GraphQL codegen plugin to generate a jest test for the Typescript SDK
Licensed under the MIT License.
FAQs
The Linear Client SDK for interacting with the Linear GraphQL API
We found that @linear/sdk demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
GitHub removed 27 malicious pull requests attempting to inject harmful code across multiple open source repositories, in another round of low-effort attacks.
Security News
RubyGems.org has added a new "maintainer" role that allows for publishing new versions of gems. This new permission type is aimed at improving security for gem owners and the service overall.
Security News
Node.js will be enforcing stricter semver-major PR policies a month before major releases to enhance stability and ensure reliable release candidates.