ya-git-jira 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/git-bump.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { createBranch, getCurrentBranch } from "../lib/git"
4
4
  import { Command } from 'commander'
@@ -27,8 +27,8 @@ export function create(): Command {
27
27
  return program
28
28
  }
29
29
 
30
+ export default create
31
+
30
32
  if (isMain('git-bump')) {
31
33
  await create().parseAsync(Bun.argv)
32
34
  }
33
-
34
- export default create
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from 'commander'
4
4
  import { myUnresolvedIssues } from "../lib/jira"
@@ -7,9 +7,9 @@ import { isMain } from '../lib/is_main'
7
7
  export function create(): Command {
8
8
  const program = new Command()
9
9
  program
10
- .name('issues')
10
+ .name('list')
11
11
  .description('List your unresolved issues')
12
- .action(async (options) => {
12
+ .action(async () => {
13
13
  const issues = await myUnresolvedIssues()
14
14
  console.log(`You have ${issues.length} unresolved issues`)
15
15
  issues.forEach(issue => {
@@ -19,8 +19,8 @@ export function create(): Command {
19
19
  return program
20
20
  }
21
21
 
22
- if (isMain('git-jira-issues')) {
22
+ export default create
23
+
24
+ if (isMain('git-jira-issue-list')) {
23
25
  await create().parseAsync(Bun.argv)
24
26
  }
25
-
26
- export default create
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { getIssue } from "../lib/jira"
5
+ import { isMain } from '../lib/is_main'
6
+ import { getJiraConfig } from '../lib/jira'
7
+
8
+ export function create(): Command {
9
+ const program = new Command()
10
+ program
11
+ .name('show')
12
+ .description('Show information about one issue')
13
+ .argument('issue', 'Issue ID')
14
+ .option('-v, --verbose', 'Verbose output')
15
+ .action(async (issueId: string, options) => {
16
+ const issue = await getIssue(issueId)
17
+ if (!issue) {
18
+ console.error(`Issue ${issueId} not found`)
19
+ process.exit(1)
20
+ }
21
+ if (options.verbose) {
22
+ console.log(issue)
23
+ } else {
24
+ const { host } = await getJiraConfig()
25
+ const summary = issue.fields.summary
26
+ const url = `https://${host}/browse/${issueId}`
27
+ console.log({ issueId, summary, url })
28
+ }
29
+ })
30
+ return program
31
+ }
32
+
33
+ export default create
34
+
35
+ if (isMain('git-jira-issue-show')) {
36
+ await create().parseAsync(Bun.argv)
37
+ }
@@ -1,38 +1,25 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from 'commander'
4
- import { getIssue } from "../lib/jira"
5
4
  import { isMain } from '../lib/is_main'
6
- import { getJiraConfig } from '../lib/jira'
5
+
6
+ import list from './git-jira-issue-list'
7
+ import show from './git-jira-issue-show'
7
8
 
8
9
  export function create(): Command {
9
10
  const program = new Command()
10
11
  program
11
12
  .name('issue')
12
- .description('Get information about an issue')
13
- .argument('issue', 'Issue ID')
14
- .option('-v, --verbose', 'Verbose output')
15
- .option('-u, --url', 'Show the URL of the issue')
16
- .action(async (issueId: string, options) => {
17
- const { host } = await getJiraConfig()
18
- const issue = await getIssue(issueId)
19
- if (!issue) {
20
- console.error(`Issue ${issueId} not found`)
21
- process.exit(1)
22
- }
23
- if (options.verbose) {
24
- console.log(issue)
25
- process.exit(0)
26
- }
27
- if (options.url) {
28
- console.log(`https://${host}/browse/${issueId}`)
29
- }
30
- })
13
+ .description('Commands for working with issues')
14
+ .addCommand(list())
15
+ .addCommand(show())
16
+ .action(() => program.help()
17
+ )
31
18
  return program
32
19
  }
33
20
 
21
+ export default create
22
+
34
23
  if (isMain('git-jira-issue')) {
35
24
  await create().parseAsync(Bun.argv)
36
25
  }
37
-
38
- export default create
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from 'commander'
4
4
  import { createBranch } from "../lib/git"
@@ -32,8 +32,8 @@ export function create(): Command {
32
32
  return program
33
33
  }
34
34
 
35
- if (isMain('git-jira-issues')) {
35
+ export default create
36
+
37
+ if (isMain('git-jira-start')) {
36
38
  await create().parseAsync(Bun.argv)
37
39
  }
38
-
39
- export default create
package/bin/git-jira.ts CHANGED
@@ -1,24 +1,24 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from 'commander'
4
4
  import { isMain } from '../lib/is_main'
5
5
  import start from './git-jira-start'
6
6
  import issue from './git-jira-issue'
7
- import issues from './git-jira-issues'
7
+ import issues from './git-jira-issue-list'
8
8
 
9
9
  export function create(): Command {
10
10
  const program = new Command()
11
11
  program
12
12
  .name('jira')
13
- .description('A set of commands for working with Jira')
13
+ .description('Commands for working with Jira')
14
14
  .addCommand(start())
15
15
  .addCommand(issue())
16
16
  .addCommand(issues())
17
17
  return program
18
18
  }
19
19
 
20
+ export default create
21
+
20
22
  if (isMain('git-jira')) {
21
23
  await create().parseAsync(Bun.argv)
22
24
  }
23
-
24
- export default create
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+ import { getGroups } from '../lib/gitlab'
6
+
7
+ export function create(): Command {
8
+ const program = new Command()
9
+ program
10
+ .name('list')
11
+ .description('List groups for the current user')
12
+ .option("-v, --verbose", "Verbose output")
13
+ .action(async (options) => {
14
+ const groups = await getGroups()
15
+ if (options.verbose)
16
+ console.log(groups)
17
+ else {
18
+ const filtered = groups.map(g => {
19
+ const { id, name, full_path } = g
20
+ return { id, name, full_path }
21
+ })
22
+ console.log(filtered)
23
+ }
24
+ })
25
+ return program
26
+ }
27
+
28
+ export default create
29
+
30
+ if (isMain('git-lab-group-list')) {
31
+ await create().parseAsync(Bun.argv)
32
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+
6
+ import list from './git-lab-group-list'
7
+
8
+ export function create(): Command {
9
+ const program = new Command()
10
+ program
11
+ .name('group')
12
+ .description('Commands for working with GitLab groups')
13
+ .addCommand(list())
14
+ .action(() => program.help())
15
+ return program
16
+ }
17
+
18
+ export default create
19
+
20
+ if (isMain('git-lab-group')) {
21
+ await create().parseAsync(Bun.argv)
22
+ }
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { whoami, type User } from "../lib/gitlab"
5
+ import { isMain } from '../lib/is_main'
6
+
7
+ export function create(): Command {
8
+ const program = new Command()
9
+ program
10
+ .name('active')
11
+ .description('List my active MRs')
12
+ .option('-v, --verbose', 'Verbose output')
13
+
14
+ .action(async (options) => {
15
+ const user: User = await whoami()
16
+ if (!user) {
17
+ console.error(`No user!`)
18
+ process.exit(1)
19
+ }
20
+ if (options.verbose) {
21
+ console.log(user)
22
+ process.exit(0)
23
+ }
24
+ else {
25
+ console.log(user.username)
26
+ }
27
+ })
28
+ return program
29
+ }
30
+
31
+ export default create
32
+
33
+ if (isMain('git-lab-merge-active')) {
34
+ await create().parseAsync(Bun.argv)
35
+ }
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { getMergeRequestsAssignedToMe, type MergeRequest } from "../lib/gitlab"
5
+ import { isMain } from '../lib/is_main'
6
+
7
+ export function create(): Command {
8
+ const program = new Command()
9
+ program
10
+ .name('todo')
11
+ .description('MRs needing my review')
12
+ .option('-v, --verbose', 'Verbose output')
13
+ .action(async (options) => {
14
+ const mrs: MergeRequest[] = await getMergeRequestsAssignedToMe()
15
+ if (options.verbose) {
16
+ console.log(mrs)
17
+ }
18
+ else {
19
+ const filtered = mrs.map(mr => {
20
+ const { id, title, web_url, source_branch, target_branch, merge_status } = mr
21
+ return { id, title, web_url, source_branch, target_branch, merge_status }
22
+ })
23
+ console.log(filtered)
24
+ }
25
+ })
26
+ return program
27
+ }
28
+
29
+ export default create
30
+
31
+ if (isMain('git-lab-merge-todo')) {
32
+ await create().parseAsync(Bun.argv)
33
+ }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+
6
+ export function create(): Command {
7
+ const program = new Command()
8
+ program
9
+ .name('train')
10
+ .description('Commands for working with GitLab merge trains')
11
+ .action(() => program.help())
12
+ return program
13
+ }
14
+
15
+ export default create
16
+
17
+ if (isMain('git-lab-merge-train')) {
18
+ await create().parseAsync(Bun.argv)
19
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+
6
+ import todo from './git-lab-merge-todo'
7
+
8
+ export function create(): Command {
9
+ const program = new Command()
10
+ program
11
+ .name('merge')
12
+ .description('Commands for working with GitLab merge requests')
13
+ .addCommand(todo())
14
+ .action(() => program.help())
15
+ return program
16
+ }
17
+
18
+ export default create
19
+
20
+ if (isMain('git-lab-merge')) {
21
+ await create().parseAsync(Bun.argv)
22
+ }
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+ import { getNamespaces } from '../lib/gitlab'
6
+
7
+ export function create(): Command {
8
+ const program = new Command()
9
+ program
10
+ .name('list')
11
+ .description('List namespaces for the current user')
12
+ .action(async () => {
13
+ const namespaces = await getNamespaces()
14
+ console.log(namespaces)
15
+ })
16
+ return program
17
+ }
18
+
19
+ export default create
20
+
21
+ if (isMain('git-lab-namespace-list')) {
22
+ await create().parseAsync(Bun.argv)
23
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+
6
+ import list from './git-lab-namespace-list'
7
+
8
+ export function create(): Command {
9
+ const program = new Command()
10
+ program
11
+ .name('namespace')
12
+ .description('Commands for working with GitLab namespaces')
13
+ .addCommand(list())
14
+ .action(() => program.help())
15
+ return program
16
+ }
17
+
18
+ export default create
19
+
20
+ if (isMain('git-lab-namespace')) {
21
+ await create().parseAsync(Bun.argv)
22
+ }
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from 'commander'
4
4
  import { getProjects, type Project } from "../lib/gitlab"
@@ -7,12 +7,12 @@ import { isMain } from '../lib/is_main'
7
7
  export function create(): Command {
8
8
  const program = new Command()
9
9
  program
10
- .name('projects')
10
+ .name('list')
11
11
  .description('List projects for current user')
12
12
  .option('-v, --verbose', 'Verbose output')
13
- .argument('[path...]', 'Namespace paths to filter by')
14
- .action(async (paths: string[], options) => {
15
- const projects: Array<Project> = await getProjects(paths)
13
+ .option('-m, --match <match>', 'Match projects with paths containing <match>')
14
+ .action(async (options) => {
15
+ const projects: Array<Project> = await getProjects(options.match)
16
16
  if (!projects) {
17
17
  console.error(`No projects!`)
18
18
  process.exit(1)
@@ -31,8 +31,8 @@ export function create(): Command {
31
31
  return program
32
32
  }
33
33
 
34
- if (isMain('git-lab-projects')) {
34
+ export default create
35
+
36
+ if (isMain('git-lab-project-list')) {
35
37
  await create().parseAsync(Bun.argv)
36
38
  }
37
-
38
- export default create
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { getProjectPipelines, type Pipeline } from "../lib/gitlab"
5
+ import { isMain } from '../lib/is_main'
6
+
7
+ export function create(): Command {
8
+ const program = new Command()
9
+ program
10
+ .name('list')
11
+ .description('List recent successful pipelines')
12
+ .option('-v, --verbose', 'Verbose output')
13
+ .option('-d, --days <days>', 'Number of days to look back', '7')
14
+ .option('-s, --status <status>', 'Status of pipelines to list: success | runnning | ', 'success')
15
+ .action(async (options) => {
16
+ const pipelines: Array<Pipeline> = await getProjectPipelines(options)
17
+ console.debug(`pipelines: ${pipelines}`)
18
+ if (!pipelines) {
19
+ console.error(`No pipelines!`)
20
+ process.exit(1)
21
+ }
22
+ if (options.verbose) {
23
+ console.log(pipelines)
24
+ }
25
+ else {
26
+ let filtered = pipelines.map((p: Pipeline) => {
27
+ const { id, web_url, updated_at, ref, sha } = p
28
+ return { id, web_url, updated_at, ref, sha }
29
+ })
30
+ console.log(filtered)
31
+ }
32
+ })
33
+ return program
34
+ }
35
+
36
+ export default create
37
+
38
+ if (isMain('git-lab-project-pipeline-list')) {
39
+ await create().parseAsync(Bun.argv)
40
+ }
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+
6
+ import list from './git-lab-project-pipeline-list'
7
+
8
+ export function create(): Command {
9
+ const program = new Command()
10
+ program
11
+ .name('pipeline')
12
+ .description('Commands for working with GitLab pipelines')
13
+ .addCommand(list())
14
+ .action(() => program.help())
15
+ return program
16
+ }
17
+
18
+ export default create
19
+
20
+ if (isMain('git-lab-project-pipeline')) {
21
+ await create().parseAsync(Bun.argv)
22
+ }
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { findProject } from "../lib/gitlab"
5
+ import { getRemote } from '../lib/git'
6
+ import { isMain } from '../lib/is_main'
7
+
8
+ export function create(): Command {
9
+ const program = new Command()
10
+ program
11
+ .name('whereami')
12
+ .description('Show current project based on current directory')
13
+ .option('-v, --verbose', 'Verbose output')
14
+ .action(async (options) => {
15
+ const ssh_url = await getRemote();
16
+ if (!ssh_url) {
17
+ console.error(`No remote!`)
18
+ process.exit(1)
19
+ }
20
+ console.log(`Remote: ${ssh_url}`)
21
+ const project = await findProject(ssh_url);
22
+ if (!project) {
23
+ console.error(`No project!`)
24
+ process.exit(1)
25
+ }
26
+ if (options.verbose) {
27
+ console.log(project)
28
+ } else {
29
+ const { id, name, path_with_namespace, ssh_url_to_repo } = project
30
+ console.log({id, name, path_with_namespace, ssh_url_to_repo })
31
+ }
32
+ })
33
+ return program
34
+ }
35
+
36
+ export default create
37
+
38
+ if (isMain('git-lab-project-whereami')) {
39
+ await create().parseAsync(Bun.argv)
40
+ }
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander'
4
+ import { isMain } from '../lib/is_main'
5
+
6
+ import list from './git-lab-project-list'
7
+ import whereami from './git-lab-project-whereami'
8
+
9
+ export function create(): Command {
10
+ const program = new Command()
11
+ program
12
+ .name('projects')
13
+ .description('Commands for working with GitLab projects')
14
+ .addCommand(list())
15
+ .addCommand(whereami())
16
+ .action(() => {
17
+ program.help()
18
+ })
19
+ return program
20
+ }
21
+
22
+ export default create
23
+
24
+ if (isMain('git-lab-project')) {
25
+ await create().parseAsync(Bun.argv)
26
+ }
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from 'commander'
4
4
  import { whoami, type User } from "../lib/gitlab"
@@ -27,8 +27,8 @@ export function create(): Command {
27
27
  return program
28
28
  }
29
29
 
30
+ export default create
31
+
30
32
  if (isMain('git-lab-whoami')) {
31
33
  await create().parseAsync(Bun.argv)
32
34
  }
33
-
34
- export default create
package/bin/git-lab.ts CHANGED
@@ -1,22 +1,30 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from 'commander'
4
4
  import { isMain } from '../lib/is_main'
5
+
6
+ import groups from './git-lab-group'
7
+ import merges from './git-lab-merge'
8
+ import namespaces from './git-lab-namespace'
9
+ import projects from './git-lab-project'
5
10
  import whoami from './git-lab-whoami'
6
- import projects from './git-lab-projects'
7
11
 
8
12
  export function create(): Command {
9
13
  const program = new Command()
10
14
  program
11
15
  .name('lab')
12
- .description('A set of commands for working with GitLab')
13
- .addCommand(whoami())
16
+ .description('Commands for working with GitLab')
17
+ .addCommand(groups())
18
+ .addCommand(merges())
19
+ .addCommand(namespaces())
14
20
  .addCommand(projects())
21
+ .addCommand(whoami())
22
+ .action(() => program.help())
15
23
  return program
16
24
  }
17
25
 
18
- if (isMain('git-jira')) {
26
+ export default create
27
+
28
+ if (isMain('git-lab')) {
19
29
  await create().parseAsync(Bun.argv)
20
30
  }
21
-
22
- export default create
package/bin/gitj.ts CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env bun run
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import bump from './git-bump'
4
4
  import jira from './git-jira'
package/build.ts CHANGED
@@ -4,7 +4,7 @@ import { glob } from 'glob'
4
4
  const result: BuildOutput = await Bun.build({
5
5
  entrypoints: ['./index.ts', ...glob.sync('./bin/*.ts')],
6
6
  outdir: './dist',
7
- target: 'bun'
7
+ target: 'bun',
8
8
  })
9
9
 
10
10
  if (result.success) {
package/bun.lockb CHANGED
Binary file
package/lib/gitlab.ts CHANGED
@@ -18,11 +18,27 @@ export async function getGitlabConfig(): Promise<GitlabConfig> {
18
18
  return { host, user, token }
19
19
  }
20
20
 
21
- export async function get(endpoint: string): Promise<JSONValue> {
21
+ function getNextLink(link: string | null): string | undefined {
22
+ if (!link) {
23
+ return undefined
24
+ }
25
+ const regex = /<([^>]+)>; rel="next"/
26
+ const match = link.match(regex)
27
+ const next = match ? match[1] : undefined
28
+ return next
29
+ }
30
+
31
+ export async function gitlabApi(endpoint: string): Promise<JSONValue> {
32
+ if (endpoint.startsWith("/")) {
33
+ console.warn(`gitlabApi: endpoint ${endpoint} starts with /, removing it`)
34
+ endpoint = endpoint.slice(1)
35
+ }
22
36
  const method = "GET"
23
37
  const { host, token } = await getGitlabConfig()
24
38
  const base = `https://${host}/api/v4`
25
- const uri = `${base}/${endpoint}`
39
+ const requested = 100
40
+ const sep = endpoint.includes('?') ? '&' : '?'
41
+ const uri = `${base}/${endpoint}${sep}per_page=${requested}`
26
42
  const headers = new Headers()
27
43
  headers.append("Accept", "application/json")
28
44
  headers.append('Private-Token', token)
@@ -30,9 +46,20 @@ export async function get(endpoint: string): Promise<JSONValue> {
30
46
  method,
31
47
  headers,
32
48
  }
33
- const request = new Request(uri, options)
49
+ let request = new Request(uri, options)
34
50
  const response = await fetch(request)
35
- return await response.json()
51
+ let link = getNextLink(response.headers.get('Link'))
52
+ let partial = (await response.json()) as Array<JSONValue>
53
+ let result: Array<JSONValue> = partial
54
+ while (partial.length == requested && link)
55
+ {
56
+ let request = new Request(link, options)
57
+ const next_response = await fetch(request)
58
+ link = getNextLink(next_response.headers.get('Link'))
59
+ partial = (await next_response.json()) as Array<JSONValue>
60
+ result = result.concat(partial)
61
+ }
62
+ return result;
36
63
  }
37
64
 
38
65
  export type User = JSONValue & {
@@ -43,7 +70,7 @@ export type User = JSONValue & {
43
70
  }
44
71
 
45
72
  export async function whoami(): Promise<User> {
46
- return await get("/user") as User
73
+ return await gitlabApi("user") as User
47
74
  }
48
75
 
49
76
  export type Project = JSONValue & {
@@ -55,32 +82,128 @@ export type Project = JSONValue & {
55
82
  ssh_url_to_repo: string
56
83
  }
57
84
 
58
- export async function getProjects(paths: string[]): Promise<Array<Project>> {
59
- let search: string = ""
60
- if (paths.length > 0) {
61
- search = '&search=' + paths.join(",")
85
+ export async function getProjects(match: string): Promise<Array<Project>> {
86
+ let search = ''
87
+ if (match) {
88
+ const m = encodeURIComponent(match)
89
+ search = `&search=${m}`
90
+ }
91
+ const projects = await gitlabApi(`projects?membership=true&simple=true${search}`)
92
+ if (!projects) {
93
+ throw new Error(`No projects!`)
94
+ } else if (!Array.isArray(projects)) {
95
+ console.log(projects)
96
+ throw new Error(`Projects is not an array!`)
62
97
  }
63
- return await get(`/projects?visibility=private&membership=true&simple=true${search}`) as Array<Project>
98
+ const projs = projects as Array<Project>
99
+
100
+ const filtered = projs.filter((p: Project): boolean => {
101
+ return p.path_with_namespace.toLowerCase().includes(match.toLowerCase())
102
+ })
103
+ return filtered
64
104
  }
65
105
 
66
106
  // git@gitlab.com:etagen-internal/linear-generator-config.git
67
- export async function findProject(ssh_url: string): Promise<Project> {
107
+ export async function findProject(ssh_url: string): Promise<Project | undefined> {
68
108
  const parts = ssh_url.split(':')
69
109
  if (parts.length != 2) {
70
110
  throw new Error(`${ssh_url} is invalid, could not be split into two parts at :`)
71
111
  }
72
112
  const name = path.basename(parts[1], '.git')
73
113
 
74
- const projects = await getProjects([name])
114
+ const projects = await getProjects(name) as Array<Project>
75
115
  const project = projects.find((p: Project): boolean => {
76
116
  return p.ssh_url_to_repo === ssh_url
77
117
  })
118
+ return project
119
+ }
120
+
121
+ export async function projectScopedGet(endpoint: string): Promise<JSONValue> {
122
+ if (endpoint.startsWith("/")) {
123
+ console.warn(`gitlabApi: endpoint ${endpoint} starts with /, removing it`)
124
+ endpoint = endpoint.slice(1)
125
+ }
126
+ const method = "GET"
127
+ const { host, token } = await getGitlabConfig()
128
+ const remote = await getRemote()
129
+ const project = await findProject(remote)
78
130
  if (!project) {
79
- throw new Error(`No project with ssh_url_to_repo ${ssh_url} found`)
131
+ throw new Error(`Could not find project for remote ${remote}`)
80
132
  }
81
- return project
133
+ const base = `https://${host}/api/v4/projects/${project.id}`
134
+ const uri = `${base}/${endpoint}`
135
+ console.debug(`projectScopedGet uri: ${uri}`)
136
+ const headers = new Headers()
137
+ headers.append("Accept", "application/json")
138
+ headers.append('Private-Token', token)
139
+ const options = {
140
+ method,
141
+ headers,
142
+ }
143
+ const request = new Request(uri, options)
144
+ const response = await fetch(request)
145
+ return await response.json()
82
146
  }
83
147
 
84
148
  export async function getMergeRequest(id: string): Promise<JSONValue> {
85
- return await get(`/merge_requests/${id}`)
149
+ return await projectScopedGet(`merge_requests/${id}`)
150
+ }
151
+
152
+ export async function getNamespaces(): Promise<JSONValue> {
153
+ return await gitlabApi(`namespaces`)
154
+ }
155
+
156
+ export type Group = JSONValue & {
157
+ id: number
158
+ name: string
159
+ full_path: string
160
+ }
161
+
162
+ export async function getGroups(): Promise<Array<Group>> {
163
+ return await gitlabApi(`groups`) as Array<Group>
164
+ }
165
+
166
+ export type MergeRequest = JSONValue & {
167
+ id: number
168
+ title : string
169
+ description : string
170
+ state : string
171
+ source_branch: string
172
+ target_branch: string
173
+ web_url: string
174
+ merge_status: string
175
+ }
176
+
177
+ export async function getMergeRequestsAssignedToMe() : Promise<Array<MergeRequest>>
178
+ {
179
+ const me = await whoami()
180
+ return await gitlabApi(`merge_requests?state=opened&reviewer_id=${me.id}`) as Array<MergeRequest>
181
+ }
182
+
183
+ export type Pipeline = JSONValue & {
184
+ id: number
185
+ status: string
186
+ ref: string
187
+ sha: string
188
+ web_url: string
189
+ updated_at: string // datetime string like "2021-03-18T15:00:00.000Z"
190
+ }
191
+
192
+ export type PipelineStatus = 'success' | 'running'
193
+
194
+ export interface GetPipelineOptions {
195
+ days: number
196
+ status: PipelineStatus
197
+ }
198
+
199
+ export async function getProjectPipelines(options: GetPipelineOptions): Promise<Array<Pipeline>> {
200
+ const { days, status } = options
201
+ const me = await whoami()
202
+ const username = me.username
203
+ const date = new Date()
204
+ const pastDate = date.getDate() - days;
205
+ date.setDate(pastDate)
206
+ const updated = date.toISOString()
207
+ console.debug(`updated: ${updated}`)
208
+ return await projectScopedGet(`pipelines?status=${status}&username=${username}&updated_after=${updated}`) as Array<Pipeline>
86
209
  }
package/lib/is_main.ts CHANGED
@@ -1,5 +1,24 @@
1
1
  import path from 'node:path'
2
+
3
+ function justBase(filename: string): string {
4
+ const ext = path.extname(filename)
5
+ const base = path.basename(filename, ext)
6
+ return base
7
+ }
8
+
2
9
  export function isMain(self: string): boolean {
3
- const exe = path.basename(Bun.argv[1]).split('.')[0]
4
- return exe == self || import.meta.main
10
+ const arg1 = Bun.argv[1]
11
+ const argv1Base = justBase(arg1)
12
+ const selfBase = justBase(self)
13
+ const result = argv1Base === selfBase
14
+ // if (result) {
15
+ // console.log({
16
+ // arg1,
17
+ // self,
18
+ // argv1Base,
19
+ // selfBase,
20
+ // result,
21
+ // })
22
+ // }
23
+ return result
5
24
  }
package/lib/jira.ts CHANGED
@@ -25,11 +25,14 @@ export async function getJiraConfig(): Promise<JiraConfig> {
25
25
  return { host, token }
26
26
  }
27
27
 
28
- export async function get(endpoint: string): Promise<JSONValue> {
28
+ export async function jiraApi(endpoint: string): Promise<JSONValue> {
29
+ if (endpoint.startsWith("/")) {
30
+ console.warn(`jiraApi: endpoint ${endpoint} starts with /`)
31
+ endpoint = endpoint.slice(1)
32
+ }
29
33
  const method = "GET"
30
34
  const { host, token } = await getJiraConfig()
31
- const base = `https://${host}/rest/api/3`
32
- const uri = `${base}/${endpoint}`
35
+ const uri = `https://${host}/rest/api/3/${endpoint}`
33
36
  const auth = `Basic ${token}`
34
37
  const headers = new Headers()
35
38
  headers.append("Authorization", auth)
@@ -40,11 +43,13 @@ export async function get(endpoint: string): Promise<JSONValue> {
40
43
  }
41
44
  const request = new Request(uri, options)
42
45
  const response = await fetch(request)
43
- return await response.json()
46
+ const result = await response.json()
47
+ return result;
44
48
  }
45
49
 
46
50
  export async function getIssue(issue: string): Promise<Issue> {
47
- return await get(`/issue/${issue}`) as Issue
51
+ const result = await jiraApi(`issue/${issue}`) as Issue
52
+ return result
48
53
  }
49
54
 
50
55
  type Myself = JSONValue & {
@@ -52,7 +57,7 @@ type Myself = JSONValue & {
52
57
  }
53
58
 
54
59
  export async function getMyself(): Promise<Myself> {
55
- return await get("/myself") as Myself
60
+ return await jiraApi("/myself") as Myself
56
61
  }
57
62
 
58
63
  type SearchResponse = JSONValue & {
@@ -63,6 +68,6 @@ export async function myUnresolvedIssues(): Promise<Array<Issue>> {
63
68
  const myself = await getMyself()
64
69
  const myselfId = myself.accountId
65
70
  const jql = `assignee = ${myselfId} AND resolution = Unresolved`
66
- const issues = await get(`/search?jql=${encodeURIComponent(jql)}`) as SearchResponse
71
+ const issues = await jiraApi(`/search?jql=${encodeURIComponent(jql)}`) as SearchResponse
67
72
  return issues.issues
68
73
  }
package/package.json CHANGED
@@ -1,16 +1,27 @@
1
1
  {
2
2
  "name": "ya-git-jira",
3
3
  "description": "git extensions for Jira integration. Assumes bun is installed.",
4
- "version": "1.3.0",
4
+ "version": "1.4.0",
5
5
  "module": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "git-bump": "dist/bin/git-bump.js",
9
+ "git-jira-issue-list": "dist/bin/git-jira-issue-list.js",
10
+ "git-jira-issue-show": "dist/bin/git-jira-issue-show.js",
9
11
  "git-jira-issue": "dist/bin/git-jira-issue.js",
10
- "git-jira-issues": "dist/bin/git-jira-issues.js",
11
12
  "git-jira-start": "dist/bin/git-jira-start.js",
12
13
  "git-jira": "dist/bin/git-jira.js",
13
- "git-lab-projects": "dist/bin/git-lab-projects.js",
14
+ "git-lab-group": "dist/bin/git-lab-group.js",
15
+ "git-lab-merge-active": "dist/bin/git-lab-merge-active.js",
16
+ "git-lab-merge-todo": "dist/bin/git-lab-merge-todo.js",
17
+ "git-lab-merge": "dist/bin/git-lab-merge.js",
18
+ "git-lab-namespace-list": "dist/bin/git-lab-namespace-list.js",
19
+ "git-lab-namespace": "dist/bin/git-lab-namespace.js",
20
+ "git-lab-project-list": "dist/bin/git-lab-project-list.js",
21
+ "git-lab-project-pipeline": "dist/bin/git-lab-project-pipeline.js",
22
+ "git-lab-project-pipeline-list": "dist/bin/git-lab-project-pipeline-list.js",
23
+ "git-lab-project-whereami": "dist/bin/git-lab-project-whereami.js",
24
+ "git-lab-project": "dist/bin/git-lab-project.js",
14
25
  "git-lab-whoami": "dist/bin/git-lab-whoami.js",
15
26
  "git-lab": "dist/bin/git-lab.js",
16
27
  "gitj": "dist/bin/gitj.js"
@@ -21,7 +32,8 @@
21
32
  },
22
33
  "devDependencies": {
23
34
  "@types/commander": "^2.12.2",
24
- "bun-types": "latest"
35
+ "bun-types": "latest",
36
+ "typescript": "latest"
25
37
  },
26
38
  "directories": {
27
39
  "lib": "lib"
@@ -0,0 +1,19 @@
1
+ import { readdirSync } from 'fs';
2
+ import { describe, expect, test } from 'bun:test';
3
+ import { doCommand } from '..';
4
+
5
+ describe('bin scripts', () => {
6
+ const binDir = './bin';
7
+
8
+ const scripts = readdirSync(binDir).filter((file) => {
9
+ return file.endsWith('.ts');
10
+ });
11
+
12
+ scripts.forEach((script) => {
13
+ const stem = script.split('/').pop()?.split('-').pop()?.split('.').shift();
14
+ test(`"${script} --help" should contain 'Usage: ${stem}'"`, async () => {
15
+ const output = await doCommand(['bun', 'run', `${binDir}/${script}`, '--help']);
16
+ expect(output).toContain(`Usage: ${stem}`);
17
+ });
18
+ });
19
+ });
package/tests/git.test.ts CHANGED
@@ -13,13 +13,13 @@ test("getRemote", async (): Promise<void> => {
13
13
  test("findProject linear-generator", async (): Promise<void> => {
14
14
  const ssh_url = "git@gitlab.com:etagen-internal/linear-generator.git"
15
15
  const project = await findProject(ssh_url)
16
- expect(project.ssh_url_to_repo).toBe(ssh_url)
17
- expect(project.id).toBe(4053065)
18
- })
16
+ expect(project?.ssh_url_to_repo).toBe(ssh_url)
17
+ expect(project?.id).toBe(4053065)
18
+ }, 15000)
19
19
 
20
20
  test("findProject eta-lib/base", async (): Promise<void> => {
21
21
  const ssh_url = "git@gitlab.com:etagen-internal/eta-lib/base.git"
22
22
  const project = await findProject(ssh_url)
23
- expect(project.ssh_url_to_repo).toBe(ssh_url)
24
- expect(project.id).toBe(42470523)
25
- })
23
+ expect(project?.ssh_url_to_repo).toBe(ssh_url)
24
+ expect(project?.id).toBe(42470523)
25
+ }, 15000)
@@ -10,7 +10,7 @@ test("gitj works", async (): Promise<void> => {
10
10
  const { out, code }: SpawnResult = await spawn(["bun", "run", "bin/gitj.ts"])
11
11
  expect(out).toMatch("Usage:")
12
12
  expect(out).toMatch("Bump the version number in the current branch")
13
- expect(out).toMatch("A set of commands for working with Jira")
13
+ expect(out).toMatch("Commands for working with Jira")
14
14
  expect(code).toBe(0)
15
15
  })
16
16