generated from vitalics/npm-bun-tsup-library
-
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
8 changed files
with
360 additions
and
17 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
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,47 @@ | ||
#!/usr/bin/env bun | ||
import colors from "picocolors"; | ||
import { Git } from "./git"; | ||
import { GitLab } from "./gitlab"; | ||
import { | ||
Display, | ||
DisplayCommit, | ||
DisplayEnvironment, | ||
DisplayMergeRequest, | ||
DisplayPipelineJobs, | ||
} from "./display"; | ||
|
||
if (!process.env.GITLAB_TOKEN) { | ||
console.error("Please set the GITLAB_TOKEN environment variable."); | ||
console.error("You can generate a new token here:"); | ||
console.error( | ||
colors.blue("> https://gitlab.com/-/user_settings/personal_access_tokens") | ||
); | ||
process.exit(1); | ||
} | ||
|
||
try { | ||
const git = new Git(); | ||
const gitlab = new GitLab(process.env.GITLAB_TOKEN); | ||
const myProjects = await gitlab.myProjectsByName(await git.repoName()); | ||
const branch = await git.branch(); | ||
|
||
for (const project of myProjects) { | ||
const pipeline = await project.pipelineBy(branch); | ||
|
||
if (pipeline) { | ||
new Display( | ||
new DisplayCommit(await project.commitBy(pipeline.sha), pipeline), | ||
new DisplayMergeRequest(await project.mergeRequestBy(branch)), | ||
new DisplayEnvironment(await project.environmentsBy(branch)), | ||
new DisplayPipelineJobs(await project.pipelineJobsBy(pipeline.id)) | ||
); | ||
} else { | ||
console.error("No pipelines found for this project."); | ||
} | ||
} | ||
} catch (error: unknown) { | ||
if (error instanceof Error) { | ||
console.error("Error fetching pipeline status:", error.message); | ||
} | ||
console.error(error); | ||
} |
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,151 @@ | ||
import colors from "picocolors"; | ||
import { parseISO, formatRelative } from "date-fns"; | ||
|
||
export class Display { | ||
rows: string[] = []; | ||
constructor(...args: unknown[]) { | ||
const rows = args[0]; | ||
|
||
if (rows instanceof Display) { | ||
(args as Display[]).forEach((display) => display.display()); | ||
} else if (Array.isArray(rows)) { | ||
this.rows = rows; | ||
} | ||
} | ||
|
||
display() { | ||
this.rows.forEach((row) => console.log(row)); | ||
} | ||
} | ||
|
||
export class DisplayEnvironment extends Display { | ||
constructor(environment: { external_url: string }) { | ||
super([ | ||
colors.bold("Environment"), | ||
`> ${colors.blue(environment.external_url)}`, | ||
"", | ||
]); | ||
} | ||
} | ||
|
||
export class DisplayMergeRequest extends Display { | ||
constructor(mr: { | ||
merged_at: string; | ||
state: string; | ||
title: string; | ||
iid: string; | ||
web_url: string; | ||
}) { | ||
const mrid = colors.red(`#${mr.iid}`); | ||
const date = colors.green( | ||
`(${formatRelative(parseISO(mr.merged_at), new Date())})` | ||
); | ||
const statusEmojis = { | ||
opened: colors.greenBright("✅ Opened"), | ||
closed: colors.redBright("❌ Closed"), | ||
merged: colors.greenBright("✅ Merged"), | ||
} as Record<string, string>; | ||
|
||
const status = statusEmojis[mr.state] || ""; | ||
|
||
super([ | ||
colors.bold("Merge Request"), | ||
`${mrid} - ${mr.title} - ${status} ${date}`, | ||
`> ${colors.blue(mr.web_url)}`, | ||
"", | ||
]); | ||
} | ||
} | ||
|
||
export class DisplayCommit extends Display { | ||
static statusEmojis = { | ||
created: colors.whiteBright("⚙️ Created"), | ||
waiting_for_resource: colors.yellowBright("⏳ Waiting for resource"), | ||
preparing: colors.yellowBright("🔄 Preparing"), | ||
pending: colors.yellowBright("💤 Pending"), | ||
running: colors.whiteBright("🚀 Running"), | ||
success: colors.greenBright("✅ Success"), | ||
failed: colors.redBright("❌ Failed"), | ||
canceled: colors.redBright("🛑 Canceled"), | ||
skipped: colors.yellowBright("⏭️ Skipped"), | ||
manual: colors.blueBright("👤 Manual"), | ||
scheduled: colors.blueBright("📅 Scheduled"), | ||
} as Record<string, string>; | ||
|
||
constructor( | ||
commit: { | ||
created_at: string; | ||
short_id: string; | ||
title: string; | ||
author_name: string; | ||
}, | ||
pipeline: { status: string; web_url: string } | ||
) { | ||
const sha = colors.red(`${commit.short_id}`); | ||
const author = colors.blueBright(`<${commit.author_name}>`); | ||
const date = colors.green( | ||
`(${formatRelative(parseISO(commit.created_at), new Date())})` | ||
); | ||
const title = colors.whiteBright(commit.title); | ||
|
||
super([ | ||
colors.bold("Commit"), | ||
`${sha} - ${title} ${date} ${author} - ${ | ||
DisplayCommit.statusEmojis[pipeline.status] | ||
}`, | ||
`> ${colors.blue(pipeline.web_url)}`, | ||
"", | ||
]); | ||
} | ||
} | ||
|
||
type PipelineJob = { | ||
status: string; | ||
stage: string; | ||
duration: number; | ||
name: string; | ||
}; | ||
|
||
export class DisplayPipelineJobs extends Display { | ||
static statusMap = { | ||
success: "🟢", | ||
failed: "🔴", | ||
canceled: "🟠", | ||
skipped: "🟡", | ||
pending: "🟣", | ||
running: "🔵", | ||
created: "⚪", | ||
} as Record<string, string>; | ||
|
||
constructor(pipelineJobs: PipelineJob[] = []) { | ||
const stages = pipelineJobs | ||
.filter((item) => item.status !== "manual") | ||
.reduce((acc, item) => { | ||
const items = (acc[item.stage] = acc[item.stage] || []); | ||
items.push(item); | ||
return acc; | ||
}, {} as Record<string, PipelineJob[]>); | ||
|
||
super( | ||
Object.entries(stages) | ||
.map(([stage, stages]) => { | ||
return [ | ||
colors.bold(stage), | ||
stages | ||
.map((stage) => { | ||
const secs = colors.gray(`(${Math.round(stage.duration)}s)`); | ||
const status = | ||
DisplayPipelineJobs.statusMap[stage.status] || | ||
stage.status || | ||
""; | ||
|
||
return String(`${status} ${stage.name} ${secs}`).trim(); | ||
}) | ||
.join(" "), | ||
"", | ||
].flat(); | ||
}) | ||
.flat() | ||
); | ||
} | ||
} |
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,33 @@ | ||
import { $ } from "bun"; | ||
|
||
export class Git { | ||
cwd: string; | ||
|
||
constructor(cwd = process.cwd()) { | ||
this.cwd = cwd; | ||
} | ||
|
||
async branch() { | ||
const stdout = await $`git rev-parse --abbrev-ref HEAD` | ||
.cwd(this.cwd) | ||
.text(); | ||
|
||
return stdout.trim(); | ||
} | ||
|
||
async remoteUrl() { | ||
const stdout = await $`git ls-remote --get-url`.cwd(this.cwd).text(); | ||
|
||
return stdout | ||
.trim() | ||
.replace(/^git@(.*?):/, "https://$1/") | ||
.replace(/[A-z0-9\-]+@/, "") | ||
.replace(/\.git$/, ""); | ||
} | ||
|
||
async repoName() { | ||
const projectName = (remoteUrl: string) => remoteUrl.split("/").pop(); | ||
|
||
return projectName(await this.remoteUrl()); | ||
} | ||
} |
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,102 @@ | ||
export class GitlabProject { | ||
GITLAB_API_PIPELINES_URL = `/projects/:id/pipelines`; | ||
GITLAB_API_PIPELINE_JOBS_URL = `/projects/:id/pipelines/:pipeline_id/jobs`; | ||
GITLAB_API_COMMIT_URL = `/projects/:id/repository/commits/:sha`; | ||
GITLAB_API_PROJECT_MRS_URL = `/projects/:id/merge_requests`; | ||
GITLAB_API_ENVS_URL = `/projects/:id/environments`; | ||
|
||
constructor(gitlab, projectId) { | ||
this.gitlab = gitlab; | ||
this.projectId = projectId; | ||
} | ||
|
||
async commitBy(sha) { | ||
return this.gitlab._get( | ||
this.GITLAB_API_COMMIT_URL.replace(":id", this.projectId).replace( | ||
":sha", | ||
sha | ||
) | ||
); | ||
} | ||
|
||
async pipelineBy(branch) { | ||
return this.gitlab | ||
._get(this.GITLAB_API_PIPELINES_URL.replace(":id", this.projectId), { | ||
ref: branch, | ||
per_page: 1, | ||
}) | ||
.then((pipelines) => pipelines[0]); | ||
} | ||
|
||
async mergeRequestBy(branch) { | ||
return this.gitlab | ||
._get(this.GITLAB_API_PROJECT_MRS_URL.replace(":id", this.projectId), { | ||
source_branch: branch, | ||
}) | ||
.then((mrs) => mrs[0]); | ||
} | ||
|
||
async environmentsBy(branch) { | ||
return this.gitlab | ||
._get(this.GITLAB_API_ENVS_URL.replace(":id", this.projectId), { | ||
search: branch, | ||
}) | ||
.then((envs) => envs[0]); | ||
} | ||
|
||
async pipelineJobsBy(pipelineId) { | ||
return this.gitlab._get( | ||
this.GITLAB_API_PIPELINE_JOBS_URL.replace(":id", this.projectId).replace( | ||
":pipeline_id", | ||
pipelineId | ||
) | ||
); | ||
} | ||
} | ||
|
||
export class GitLab { | ||
BASE_URL = "https://gitlab.com/api/v4"; | ||
GITLAB_API_PROJECTS_URL = `/projects`; | ||
|
||
constructor(token) { | ||
this.token = token; | ||
} | ||
|
||
async _get(url, params = {}) { | ||
return fetch( | ||
`${this.BASE_URL}${url}?${new URLSearchParams(params).toString()}`, | ||
{ | ||
headers: { "Private-Token": this.token }, | ||
params, | ||
} | ||
).then((res) => res.json()); | ||
} | ||
|
||
async projectsByName(name) { | ||
return this._get(this.GITLAB_API_PROJECTS_URL, { | ||
search: name, | ||
per_page: 100, | ||
}).then((projects) => | ||
projects.map((item) => ({ | ||
id: item.id, | ||
name: item.name, | ||
ssh_url_to_repo: item.ssh_url_to_repo, | ||
http_url_to_repo: item.http_url_to_repo, | ||
web_url: item.web_url, | ||
})) | ||
); | ||
} | ||
|
||
async myProjectsByName(name) { | ||
const filterProjects = (projects, name) => | ||
projects.filter( | ||
(project) => | ||
project.ssh_url_to_repo.includes(name) || | ||
project.http_url_to_repo.includes(name) | ||
); | ||
|
||
return filterProjects(await this.projectsByName(name), name).map( | ||
(project) => new GitlabProject(this, project.id) | ||
); | ||
} | ||
} |
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,3 +1,3 @@ | ||
export default function hello() { | ||
return 'world' | ||
} | ||
export * from "./display"; | ||
export * from "./git"; | ||
export * from "./gitlab"; |
Oops, something went wrong.