Skip to content

Commit a88ecd7

Browse files
authoredSep 18, 2024··
✨ openapi-typescript (#1)
* ✨ openapi-typescript * 🐛 Fix base64 encoding in browser * 🚨 Fix pnpm lint * 🐛 Fix environment variables in node.js.yaml and update test assertion in repositories.test.ts
1 parent d5821cf commit a88ecd7

29 files changed

+152964
-41
lines changed
 

‎.env

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
BITBUCKET_CLOUD_URL=https://api.bitbucket.org/2.0
2+
BITBUCKET_CLOUD_USERNAME=
3+
BITBUCKET_CLOUD_APP_PASSWORD=
4+
5+
BITBUCKET_SERVER_URL=
6+
BITBUCKET_SERVER_TOKEN=

‎.github/workflows/node.js.yaml

+5
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,8 @@ jobs:
2323
- run: pnpm run build --noEmit
2424
- run: pnpm run lint
2525
- run: pnpm run test run
26+
env:
27+
BITBUCKET_CLOUD_USERNAME: ${{ secrets.BITBUCKET_CLOUD_USERNAME }}
28+
BITBUCKET_CLOUD_APP_PASSWORD: ${{ secrets.BITBUCKET_CLOUD_APP_PASSWORD }}
29+
BITBUCKET_SERVER_URL: ${{ secrets.BITBUCKET_SERVER_URL }}
30+
BITBUCKET_SERVER_TOKEN: ${{ secrets.BITBUCKET_SERVER_TOKEN }}

‎.markdownlint.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
default: true
22
line-length: false
33
no-inline-html: false
4+
no-hard-tabs:
5+
code_blocks: false
6+
spaces_per_tab: 2

‎README.md

+46
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,49 @@
33
[![Node.js CI](https://github.com/coderabbitai/bitbucket/actions/workflows/node.js.yaml/badge.svg)](https://github.com/coderabbitai/bitbucket/actions/workflows/node.js.yaml) [![Dependabot Updates](https://github.com/coderabbitai/bitbucket/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/coderabbitai/bitbucket/actions/workflows/dependabot/dependabot-updates) [![GitHub Pages](https://github.com/coderabbitai/bitbucket/actions/workflows/github-pages.yaml/badge.svg)](https://github.com/coderabbitai/bitbucket/actions/workflows/github-pages.yaml)
44

55
CodeRabbit's TypeScript API client for connecting to Bitbucket Cloud and Bitbucker Data Center.
6+
7+
- Bitbucket Cloud API docs: <https://developer.atlassian.com/cloud/bitbucket/rest>
8+
- Bitbucket Data Center API docs: <https://developer.atlassian.com/server/bitbucket/rest>
9+
10+
This client is auto-generated by [https://github.com/openapi-ts/openapi-typescript](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-typescript) using the OpenAPI schema from the documentation above.
11+
12+
## Usage
13+
14+
```sh
15+
pnpm i @coderabbitai/bitbucket
16+
```
17+
18+
### Cloud
19+
20+
```ts
21+
import { createBitbucketCloudClient, toBase64 } from "@coderabbitai/bitbucket"
22+
import {
23+
BITBUCKET_CLOUD_APP_PASSWORD,
24+
BITBUCKET_CLOUD_URL,
25+
BITBUCKET_CLOUD_USERNAME,
26+
} from "./env.js"
27+
28+
const basic = toBase64(
29+
BITBUCKET_CLOUD_USERNAME + ":" + BITBUCKET_CLOUD_APP_PASSWORD,
30+
)
31+
32+
export const client = createBitbucketCloudClient({
33+
baseUrl: BITBUCKET_CLOUD_URL.toString(),
34+
headers: { Accept: "application/json", Authorization: `Basic ${basic}` },
35+
})
36+
```
37+
38+
### Server
39+
40+
```ts
41+
import { createBitbucketServerClient } from "@coderabbitai/bitbucket"
42+
import { BITBUCKET_SERVER_TOKEN, BITBUCKET_SERVER_URL } from "./env.js"
43+
44+
export const server = createBitbucketServerClient({
45+
baseUrl: BITBUCKET_SERVER_URL,
46+
headers: {
47+
Accept: "application/json",
48+
Authorization: `Bearer ${BITBUCKET_SERVER_TOKEN}`,
49+
},
50+
})
51+
```

‎package.json

+13-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,14 @@
3636
"lint": "eslint . && markdownlint-cli2 \"**/*.md\" && prettier --check .",
3737
"lint:fix": "eslint --fix --quiet .; markdownlint-cli2 \"**/*.md\" --fix; prettier --list-different --write .",
3838
"start": "node dist/main.js",
39-
"test": "vitest"
39+
"test": "vitest",
40+
"openapi-typescript": "pnpm run openapi-typescript:cloud && pnpm run openapi-typescript:server",
41+
"preopenapi-typescript:cloud": "curl --output ./src/cloud/openapi/swagger.v3.json https://dac-static.atlassian.com/cloud/bitbucket/swagger.v3.json",
42+
"openapi-typescript:cloud": "openapi-typescript ./src/cloud/openapi/swagger.v3.json --output ./src/cloud/openapi/openapi-typescript.ts --immutable --empty-objects-unknown --alphabetize --root-types",
43+
"postopenapi-typescript:cloud": "ts-autofix ./src/cloud/openapi && pnpm run lint:fix",
44+
"preopenapi-typescript:server": "curl --output ./src/server/openapi/swagger.v3.json https://dac-static.atlassian.com/server/bitbucket/9.1.swagger.v3.json",
45+
"openapi-typescript:server": "openapi-typescript ./src/server/openapi/swagger.v3.json --output ./src/server/openapi/openapi-typescript.ts --immutable --empty-objects-unknown --alphabetize --root-types",
46+
"postopenapi-typescript:server": "ts-autofix ./src/server/openapi && pnpm run lint:fix"
4047
},
4148
"devDependencies": {
4249
"@eslint/js": "^9.10.0",
@@ -46,6 +53,7 @@
4653
"eslint-config-prettier": "^9.1.0",
4754
"globals": "^15.9.0",
4855
"markdownlint-cli2": "^0.14.0",
56+
"openapi-typescript": "^7.4.0",
4957
"prettier": "^3.3.3",
5058
"tsx": "^4.19.1",
5159
"typedoc": "^0.26.7",
@@ -61,5 +69,8 @@
6169
}
6270
},
6371
"types": "dist/index.d.ts",
64-
"module": "dist/index.js"
72+
"module": "dist/index.js",
73+
"dependencies": {
74+
"openapi-fetch": "^0.12.0"
75+
}
6576
}

‎pnpm-lock.yaml

+313-9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/base64.test.ts

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { test } from "vitest"
2+
import { fromBase64, toBase64 } from "./base64.js"
3+
4+
test("toBase64", ({ expect }) => {
5+
const based = toBase64("Copyright © 2024 CodeRabbit")
6+
expect(based).toBe("Q29weXJpZ2h0IMKpIDIwMjQgQ29kZVJhYmJpdA==")
7+
})
8+
9+
test("fromBase64", ({ expect }) => {
10+
const debased = fromBase64("Q29weXJpZ2h0IMKpIDIwMjQgQ29kZVJhYmJpdA==")
11+
expect(debased).toBe("Copyright © 2024 CodeRabbit")
12+
})

‎src/base64.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export function toBase64(input: string): string {
2+
const bytes = new TextEncoder().encode(input)
3+
const string = String.fromCodePoint(...bytes)
4+
return btoa(string)
5+
}
6+
7+
export function fromBase64(base64: string): Buffer | Uint8Array | string {
8+
const string = atob(base64)
9+
const bytes = Uint8Array.from(string, v => v.codePointAt(0) ?? 0)
10+
return new TextDecoder().decode(bytes)
11+
}

‎src/cloud/client.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from "vitest"
2+
import { createBitbucketCloudClient } from "./client.js"
3+
4+
test("createBitbucketCloudClient", ({ expect }) => {
5+
const client = createBitbucketCloudClient()
6+
expect(client).toBeDefined()
7+
})

‎src/cloud/client.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { Client, ClientOptions } from "openapi-fetch"
2+
import createClient from "openapi-fetch"
3+
import type { paths } from "./openapi/index.js"
4+
5+
/**
6+
* Creates an `openapi-fetch` client using {@link createClient}.
7+
*
8+
* @example
9+
* export client = createBitbucketCloudClient({
10+
* baseUrl: "https://api.bitbucket.org/2.0",
11+
* headers: { Accept: "application/json", Authorization: `Basic ${basic}` },
12+
* })
13+
*/
14+
export const createBitbucketCloudClient: (
15+
clientOptions?: ClientOptions,
16+
) => Client<paths, "application/json"> = createClient<paths, "application/json">

‎src/cloud/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./client.js"
2+
export * as cloud from "./openapi/index.js"

‎src/cloud/openapi/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./openapi-typescript.js"

‎src/cloud/openapi/openapi-typescript.ts

+26,228
Large diffs are not rendered by default.

‎src/cloud/openapi/swagger.v3.json

+32,618
Large diffs are not rendered by default.

‎src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./base64.js"
2+
export * from "./cloud/index.js"
3+
export * from "./server/index.js"

‎src/main.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
console.log("CLI mode not supported.")
1+
console.log("@coderabbitai/bitbucket")

‎src/server/client.test.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { test } from "vitest"
2+
import { createBitbucketServerClient } from "./client.js"
3+
4+
test("createBitbucketServerClient", ({ expect }) => {
5+
const client = createBitbucketServerClient()
6+
expect(client).toBeDefined()
7+
})

‎src/server/client.ts

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { Client, ClientOptions } from "openapi-fetch"
2+
import createClient from "openapi-fetch"
3+
import type { paths } from "./openapi/index.js"
4+
5+
/**
6+
* Creates an `openapi-fetch` client using {@link createClient}.
7+
*
8+
* @example
9+
* export const client = createBitbucketServerClient({
10+
* baseUrl: "https://example.org/rest",
11+
* headers: {
12+
* Accept: "application/json",
13+
* Authorization: `Bearer ${BITBUCKET_SERVER_TOKEN}`,
14+
* },
15+
* })
16+
*/
17+
export const createBitbucketServerClient: (
18+
clientOptions?: ClientOptions,
19+
) => Client<paths, "application/json"> = createClient<paths, "application/json">

‎src/server/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from "./client.js"
2+
export * as server from "./openapi/index.js"

‎src/server/openapi/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./openapi-typescript.js"

‎src/server/openapi/openapi-typescript.ts

+41,184
Large diffs are not rendered by default.

‎src/server/openapi/swagger.v3.json

+52,326
Large diffs are not rendered by default.

‎tests/cloud/client.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { createBitbucketCloudClient, toBase64 } from "../../src/index.js"
2+
import {
3+
BITBUCKET_CLOUD_APP_PASSWORD,
4+
BITBUCKET_CLOUD_URL,
5+
BITBUCKET_CLOUD_USERNAME,
6+
} from "../env.js"
7+
8+
const basic = toBase64(
9+
BITBUCKET_CLOUD_USERNAME + ":" + BITBUCKET_CLOUD_APP_PASSWORD,
10+
)
11+
12+
export const cloud = createBitbucketCloudClient({
13+
baseUrl: BITBUCKET_CLOUD_URL.toString(),
14+
headers: { Accept: "application/json", Authorization: `Basic ${basic}` },
15+
})

‎tests/cloud/repositories.test.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { test } from "vitest"
2+
import { cloud } from "./client.js"
3+
4+
test("GET /repositories", async ({ expect }) => {
5+
const got = await cloud.GET("/repositories")
6+
7+
expect(got.data?.next).toBeTypeOf("string")
8+
expect(got.data?.pagelen).toBeTypeOf("number")
9+
expect(got.data?.values).toBeDefined()
10+
})

‎tests/env.ts

+36-24
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,27 @@ import path from "path"
88
export type NodeEnv = (typeof nodeEnvs)[keyof typeof nodeEnvs]
99
export type ProcessEnv = typeof process.env
1010

11-
export function isNodeEnv(value: unknown): value is NodeEnv {
12-
return Object.values<unknown>(nodeEnvs).includes(value)
11+
interface LoadedEnv extends ProcessEnv {
12+
readonly NODE_ENV: NodeEnv
1313
}
1414

15-
export function toNodeEnv(value: unknown): NodeEnv {
16-
if (isNodeEnv(value)) return value
17-
return nodeEnvs.development
15+
function envString(key: string) {
16+
const value = parsed[key]
17+
if (!value) throw new Error(`$${key} is missing`)
18+
return value
1819
}
1920

20-
const nodeEnvs = {
21-
development: "development",
22-
production: "production",
23-
/**
24-
* Vitest sets `NODE_ENV` to `test` if it wasn't set before.
25-
* @see https://vitest.dev/guide/migration.html#envs
26-
*/
27-
test: "test",
28-
} as const
21+
function envUrl(key: string) {
22+
const str = envString(key)
23+
try {
24+
return new URL(str)
25+
} catch (error) {
26+
throw new Error(`$${key} is not a URL: ${str}`, { cause: error })
27+
}
28+
}
2929

30-
interface LoadedEnv extends ProcessEnv {
31-
readonly NODE_ENV: NodeEnv
30+
export function isNodeEnv(value: unknown): value is NodeEnv {
31+
return Object.values<unknown>(nodeEnvs).includes(value)
3232
}
3333

3434
/** Loads environment variables from the `.env` files. `NODE_ENV` has to be
@@ -66,13 +66,25 @@ function loadEnv(): LoadedEnv {
6666
return merged
6767
}
6868

69-
const parsed = loadEnv()
70-
71-
function envString(key: string) {
72-
const value = parsed[key]
73-
if (!value) throw new Error(`$${key} is missing`)
74-
return value
69+
export function toNodeEnv(value: unknown): NodeEnv {
70+
if (isNodeEnv(value)) return value
71+
return nodeEnvs.development
7572
}
7673

77-
export const BITBUCKET_CLOUD_URL = envString("BITBUCKET_CLOUD_URL")
78-
export const BITBUCKET_CLOUD_TOKEN = envString("BITBUCKET_CLOUD_TOKEN")
74+
const nodeEnvs = {
75+
development: "development",
76+
production: "production",
77+
/**
78+
* Vitest sets `NODE_ENV` to `test` if it wasn't set before.
79+
* @see https://vitest.dev/guide/migration.html#envs
80+
*/
81+
test: "test",
82+
} as const
83+
const parsed = loadEnv()
84+
export const BITBUCKET_CLOUD_URL = envUrl("BITBUCKET_CLOUD_URL")
85+
export const BITBUCKET_CLOUD_USERNAME = envString("BITBUCKET_CLOUD_USERNAME")
86+
export const BITBUCKET_CLOUD_APP_PASSWORD = envString(
87+
"BITBUCKET_CLOUD_APP_PASSWORD",
88+
)
89+
export const BITBUCKET_SERVER_URL = envUrl("BITBUCKET_SERVER_URL")
90+
export const BITBUCKET_SERVER_TOKEN = envString("BITBUCKET_SERVER_TOKEN")

‎tests/server/client.ts

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { createBitbucketServerClient } from "../../src/index.js"
2+
import { BITBUCKET_SERVER_TOKEN, BITBUCKET_SERVER_URL } from "../env.js"
3+
4+
export const server = createBitbucketServerClient({
5+
baseUrl: BITBUCKET_SERVER_URL.toString(),
6+
headers: {
7+
Accept: "application/json",
8+
Authorization: `Bearer ${BITBUCKET_SERVER_TOKEN}`,
9+
},
10+
})

‎tests/server/repositories.test.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { test } from "vitest"
2+
import { server } from "./client.js"
3+
4+
test("GET /api/latest/repos", async ({ expect }) => {
5+
const got = await server.GET("/api/latest/repos")
6+
7+
expect(got.data?.size).toBeTypeOf("number")
8+
})

‎tsconfig.eslint.json

+60-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,62 @@
11
{
2-
"extends": "./tsconfig.json",
3-
"compilerOptions": { "checkJs": true },
4-
"include": ["src", "tests"],
5-
"files": ["eslint.config.js", "./tests/env.ts"]
2+
"compilerOptions": {
3+
/* Projects */
4+
"incremental": true,
5+
"composite": true,
6+
7+
/* Language and Environment */
8+
"target": "ESNext",
9+
"lib": ["DOM", "ESNext"],
10+
"useDefineForClassFields": true,
11+
"moduleDetection": "force",
12+
13+
/* Modules */
14+
"module": "NodeNext",
15+
"moduleResolution": "NodeNext",
16+
"typeRoots": ["./node_modules/@types", "./src/types"],
17+
"resolvePackageJsonExports": true,
18+
"resolvePackageJsonImports": true,
19+
"resolveJsonModule": true,
20+
21+
/* JavaScript Support */
22+
"checkJs": true,
23+
24+
/* Emit */
25+
"declaration": true,
26+
"declarationMap": true,
27+
"sourceMap": true,
28+
"outDir": "./dist",
29+
30+
/* Interop Constraints */
31+
"isolatedModules": true,
32+
"verbatimModuleSyntax": true,
33+
"isolatedDeclarations": true,
34+
"forceConsistentCasingInFileNames": true,
35+
36+
/* Type Checking */
37+
"strict": true,
38+
"noImplicitAny": true,
39+
"strictNullChecks": true,
40+
"strictFunctionTypes": true,
41+
"strictBindCallApply": true,
42+
"strictPropertyInitialization": true,
43+
"noImplicitThis": true,
44+
"useUnknownInCatchVariables": true,
45+
"alwaysStrict": true,
46+
"noUnusedLocals": true,
47+
"noUnusedParameters": true,
48+
"exactOptionalPropertyTypes": true,
49+
"noImplicitReturns": true,
50+
"noFallthroughCasesInSwitch": true,
51+
"noUncheckedIndexedAccess": true,
52+
"noImplicitOverride": true,
53+
"noPropertyAccessFromIndexSignature": true,
54+
"allowUnusedLabels": false,
55+
"allowUnreachableCode": false,
56+
57+
/* Completeness */
58+
"skipDefaultLibCheck": true,
59+
"skipLibCheck": true
60+
},
61+
"include": ["eslint.config.js", "src", "tests"]
662
}

‎tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,5 @@
5959
"skipLibCheck": true
6060
},
6161
"include": ["src"],
62-
"exclude": ["tests", "eslint.config.js"]
62+
"exclude": ["eslint.config.js", "tests"]
6363
}

0 commit comments

Comments
 (0)
Please sign in to comment.