-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
326 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: CI | ||
|
||
on: push | ||
|
||
jobs: | ||
check: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v3 | ||
|
||
- uses: denoland/setup-deno@v2 | ||
with: | ||
deno-version: v2.x | ||
|
||
- name: Check formatting | ||
run: deno fmt --check | ||
|
||
- name: Lint code | ||
run: deno lint | ||
|
||
- name: Type check | ||
run: deno check main.ts |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# .github/workflows/publish.yml | ||
|
||
name: Publish | ||
|
||
on: | ||
push: | ||
branches: | ||
- main | ||
|
||
jobs: | ||
publish: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: read | ||
id-token: write # The OIDC ID token is used for authentication with JSR. | ||
steps: | ||
- uses: actions/checkout@v4 | ||
- run: npx jsr publish |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,19 @@ | ||
{ | ||
"version": "0.0.1", | ||
"license": "MIT", | ||
"tasks": { | ||
"dev": "deno run --watch main.ts" | ||
"dev": "deno run --allow-env --allow-sys --allow-run --allow-read --allow-net main.ts", | ||
"install": "deno install --allow-env --allow-sys --allow-run --allow-read --allow-net -c deno.json -g -n linear main.ts" | ||
}, | ||
"imports": { | ||
"@std/assert": "jsr:@std/assert@1" | ||
"@cliffy/command": "jsr:@cliffy/command@^1.0.0-rc.7", | ||
"@littletof/charmd": "jsr:@littletof/charmd@^0.1.2", | ||
"@opensrc/deno-open": "jsr:@opensrc/deno-open@^1.0.0", | ||
"@std/assert": "jsr:@std/assert@1", | ||
"@std/encoding": "jsr:@std/encoding@^1.0.5", | ||
"@std/path": "jsr:@std/path@^1.0.8" | ||
}, | ||
"publish": { | ||
"include": ["main.ts", "README.md"] | ||
} | ||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,173 @@ | ||
export function add(a: number, b: number): number { | ||
return a + b; | ||
import { Command } from "@cliffy/command"; | ||
import { open } from "@opensrc/deno-open"; | ||
import { CompletionsCommand } from "@cliffy/command/completions"; | ||
import denoConfig from "./deno.json" with { type: "json" }; | ||
import { encodeBase64 } from "@std/encoding/base64"; | ||
import { renderMarkdown } from "@littletof/charmd"; | ||
import { basename } from "@std/path"; | ||
|
||
async function getCurrentBranch(): Promise<string> { | ||
const process = new Deno.Command("git", { | ||
args: ["symbolic-ref", "--short", "HEAD"], | ||
}); | ||
const { stdout } = await process.output(); | ||
return new TextDecoder().decode(stdout).trim(); | ||
} | ||
|
||
async function getRepoDir(): Promise<string> { | ||
const process = new Deno.Command("git", { | ||
args: ["rev-parse", "--show-toplevel"], | ||
}); | ||
const { stdout } = await process.output(); | ||
const fullPath = new TextDecoder().decode(stdout).trim(); | ||
return basename(fullPath); | ||
} | ||
|
||
async function getIssueId(): Promise<string | null> { | ||
const branch = await getCurrentBranch(); | ||
const match = branch.match(/[a-zA-Z]{2,5}-[1-9][0-9]*/i); | ||
return match ? match[0].toUpperCase() : null; | ||
} | ||
|
||
async function getTeamId(): Promise<string | null> { | ||
const dir = await getRepoDir(); | ||
const match = dir.match(/^[a-zA-Z]{2,5}/); | ||
return match ? match[0].toUpperCase() : null; | ||
} | ||
|
||
async function fetchGraphQL(query: string, variables: Record<string, unknown>) { | ||
const apiKey = Deno.env.get("LINEAR_API_KEY"); | ||
if (!apiKey) { | ||
throw new Error("LINEAR_API_KEY environment variable is not set."); | ||
} | ||
|
||
const response = await fetch("https://api.linear.app/graphql", { | ||
method: "POST", | ||
headers: { | ||
"Content-Type": "application/json", | ||
"Authorization": apiKey, | ||
}, | ||
body: JSON.stringify({ query, variables }), | ||
}); | ||
|
||
const data = await response.json(); | ||
if (data.errors) { | ||
throw new Error(JSON.stringify(data.errors, null, 2)); | ||
} | ||
return data; | ||
} | ||
|
||
// Learn more at https://docs.deno.com/runtime/manual/examples/module_metadata#concepts | ||
if (import.meta.main) { | ||
console.log("Add 2 + 3 =", add(2, 3)); | ||
async function fetchIssueDetails( | ||
issueId: string, | ||
): Promise<{ title: string; description: string | null }> { | ||
const query = `query($id: String!) { issue(id: $id) { title, description } }`; | ||
const data = await fetchGraphQL(query, { id: issueId }); | ||
return data.data.issue; | ||
} | ||
|
||
async function openTeamPage() { | ||
const teamId = await getTeamId(); | ||
if (!teamId) { | ||
console.error("Could not determine team id from directory name."); | ||
Deno.exit(1); | ||
} | ||
|
||
const workspace = Deno.env.get("LINEAR_WORKSPACE"); | ||
if (!workspace) { | ||
console.error("LINEAR_WORKSPACE environment variable is not set."); | ||
Deno.exit(1); | ||
} | ||
|
||
const filterObj = { | ||
"and": [{ "assignee": { "or": [{ "isMe": { "eq": true } }] } }], | ||
}; | ||
const filter = encodeBase64(JSON.stringify(filterObj)).replace(/=/g, ""); | ||
const url = | ||
`https://linear.app/${workspace}/team/${teamId}/active?filter=${filter}`; | ||
await open(url, { app: { name: "Linear" } }); | ||
} | ||
|
||
async function openIssuePage() { | ||
const issueId = await getIssueId(); | ||
if (!issueId) { | ||
console.error( | ||
"The current branch does not contain a valid linear issue id.", | ||
); | ||
Deno.exit(1); | ||
} | ||
|
||
const workspace = Deno.env.get("LINEAR_WORKSPACE"); | ||
if (!workspace) { | ||
console.error("LINEAR_WORKSPACE environment variable is not set."); | ||
Deno.exit(1); | ||
} | ||
|
||
const url = `https://linear.app/${workspace}/issue/${issueId}`; | ||
console.log(`Opening ${url} in Linear.app`); | ||
await open(url, { app: { name: "Linear" } }); | ||
} | ||
|
||
const teamCommand = new Command() | ||
.description("Manage Linear teams") | ||
.action(openTeamPage) | ||
.command("open", "Open the team page in Linear.app") | ||
.action(openTeamPage) | ||
.command("id", "Print the team id derived from the repository name") | ||
.action(async () => { | ||
const teamId = await getTeamId(); | ||
if (teamId) { | ||
console.log(teamId); | ||
} else { | ||
console.error("Could not determine team id from directory name."); | ||
Deno.exit(1); | ||
} | ||
}); | ||
|
||
const issueCommand = new Command() | ||
.description("Manage Linear issues") | ||
.action(openIssuePage) | ||
.command("open", "Open the issue in Linear.app") | ||
.action(openIssuePage) | ||
.command("print", "Print the issue details") | ||
.option("--no-color", "Disable colored output") | ||
.action(async ({ color }) => { | ||
const issueId = await getIssueId(); | ||
if (!issueId) { | ||
console.error( | ||
"The current branch does not contain a valid linear issue id.", | ||
); | ||
Deno.exit(1); | ||
} | ||
|
||
const { title, description } = await fetchIssueDetails(issueId); | ||
const markdown = `# ${title}${description ? "\n\n" + description : ""}`; | ||
if (color && Deno.stderr.isTerminal()) { | ||
console.log(renderMarkdown(markdown)); | ||
} else { | ||
console.log(markdown); | ||
} | ||
}) | ||
.command("id", "Print the issue id in the current git branch") | ||
.action(async () => { | ||
const issueId = await getIssueId(); | ||
if (issueId) { | ||
console.log(issueId); | ||
} else { | ||
console.error( | ||
"The current branch does not contain a valid linear issue id.", | ||
); | ||
Deno.exit(1); | ||
} | ||
}); | ||
|
||
await new Command() | ||
.name("linear") | ||
.version(denoConfig.version) | ||
.description("Handy linear commands from the command line") | ||
.action(() => { | ||
console.log("Use --help to see available commands"); | ||
}) | ||
.command("issue", issueCommand) | ||
.command("team", teamCommand) | ||
.command("completions", new CompletionsCommand()) | ||
.parse(Deno.args); |
This file was deleted.
Oops, something went wrong.