├── .nvmrc ├── netlify.toml ├── src ├── client │ ├── index.ts │ ├── utils │ │ └── pick.ts │ ├── factory.ts │ ├── register-plugins.ts │ ├── constructor.ts │ ├── get-endpoint-options.ts │ └── types.ts ├── plugins │ ├── register-api-endpoints │ │ ├── types.ts │ │ └── index.ts │ ├── validate-request │ │ ├── types.ts │ │ ├── index.ts │ │ └── validate.ts │ ├── notice │ │ ├── types.ts │ │ └── index.ts │ ├── _oauth │ │ ├── index.js │ │ ├── spec.json │ │ └── routes.js │ ├── pagination │ │ ├── types.ts │ │ ├── index.ts │ │ └── get-page.ts │ ├── auth │ │ ├── validate-options.ts │ │ ├── types.ts │ │ ├── before-request.ts │ │ └── index.ts │ ├── register-endpoints │ │ ├── index.ts │ │ ├── types.ts │ │ └── register-endpoints.ts │ └── authenticate │ │ ├── types.ts │ │ ├── index.ts │ │ ├── before-request.ts │ │ └── authenticate.ts ├── request │ ├── index.ts │ ├── types.ts │ ├── with-defaults.ts │ └── fetch-wrapper.ts ├── endpoint │ ├── index.ts │ ├── spec.json │ ├── defaults.ts │ ├── utils │ │ ├── extract-url-variable-names.ts │ │ └── add-query-parameters.ts │ ├── endpoint-with-defaults.ts │ ├── with-defaults.ts │ ├── merge.ts │ ├── types.ts │ └── parse.ts ├── utils │ ├── btoa-lite.ts │ └── lower-case-header-fields.ts ├── error │ ├── types.ts │ └── index.ts ├── minimal.ts ├── index.ts └── .eslintrc.js ├── .eslintignore ├── .prettierignore ├── .prettierrc.js ├── .eslintrc.js ├── .lintstagedrc.js ├── scripts ├── generate-routes │ ├── set-method.js │ ├── set-url.js │ ├── process-deprecated.js │ ├── process-produces.js │ ├── process-consumes.js │ ├── set-alias.js │ ├── process-responses.js │ ├── process-parameters.js │ └── index.js ├── utils │ ├── extract-namespace-from-url.js │ ├── pascal-case.js │ ├── get-duplicates.js │ ├── extract-namespace-names.js │ ├── is-paginated-endpoint.js │ ├── write-to-file.js │ └── extract-endpoint-names-for-namespace.js ├── update.sh ├── generate-specification.js ├── generate-types-schema.js ├── generate-endpoint-names.js ├── generate-types.js └── generate-api-docs.js ├── .huskyrc.js ├── templates ├── minimal.d.ts.mustache ├── index.d.ts.mustache ├── plugins │ └── authenticate.d.ts.mustache └── bitbucket.d.ts.mustache ├── apidoc.json ├── tsconfig.json ├── .bilirc.ts ├── LICENSE ├── .gitignore ├── v1_API_NAME_CHANGES.md ├── specification ├── securityDefinitions.json ├── extras │ ├── paths.json │ └── endpoint-names.json └── tags.json ├── package.json ├── README.md └── CHANGELOG.md /.nvmrc: -------------------------------------------------------------------------------- 1 | lts/dubnium 2 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "docs" 3 | command = "npm run docs:build" 4 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | import { factory } from './factory' 2 | 3 | export const Client = factory() 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | docs 3 | lib 4 | 5 | # Dependency directories 6 | node_modules 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Build directories 2 | docs 3 | lib 4 | 5 | # Dependency directories 6 | node_modules 7 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | endOfLine: 'lf', 3 | semi: false, 4 | singleQuote: true, 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['standard', 'plugin:prettier/recommended', 'prettier/standard'], 3 | } 4 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '*.{ts}': () => 'tsc -p tsconfig.json --noEmit', 3 | 'scripts/**/*.js': ['eslint'], 4 | 'src/**/*.ts': ['eslint'], 5 | } 6 | -------------------------------------------------------------------------------- /src/plugins/register-api-endpoints/types.ts: -------------------------------------------------------------------------------- 1 | export type APIClient = import('../../client/types').APIClient 2 | export type Options = import('../../client/types').Options 3 | -------------------------------------------------------------------------------- /src/request/index.ts: -------------------------------------------------------------------------------- 1 | import { endpoint } from '../endpoint' 2 | import { withDefaults } from './with-defaults' 3 | 4 | export const request = withDefaults(endpoint, {}) 5 | -------------------------------------------------------------------------------- /src/endpoint/index.ts: -------------------------------------------------------------------------------- 1 | import { DEFAULTS } from './defaults' 2 | import { withDefaults } from './with-defaults' 3 | 4 | export const endpoint = withDefaults(null, DEFAULTS) 5 | -------------------------------------------------------------------------------- /src/plugins/validate-request/types.ts: -------------------------------------------------------------------------------- 1 | export type APIClient = import('../../client/types').APIClient 2 | export type EndpointParams = import('../../endpoint/types').EndpointParams 3 | -------------------------------------------------------------------------------- /scripts/generate-routes/set-method.js: -------------------------------------------------------------------------------- 1 | const setMethod = (endpointObject, method) => { 2 | endpointObject.method = method.toUpperCase() 3 | } 4 | 5 | module.exports.setMethod = setMethod 6 | -------------------------------------------------------------------------------- /scripts/utils/extract-namespace-from-url.js: -------------------------------------------------------------------------------- 1 | const extractNamespaceFromURL = (url) => /^\/(\w+)\/?/.exec(url)[1] 2 | 3 | module.exports.extractNamespaceFromURL = extractNamespaceFromURL 4 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | const tasks = (taskList) => taskList.join(' && ') 2 | 3 | module.exports = { 4 | hooks: { 5 | 'pre-commit': tasks(['lint-staged', 'pretty-quick --staged']), 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /scripts/utils/pascal-case.js: -------------------------------------------------------------------------------- 1 | const { camelCase, upperFirst } = require('lodash') 2 | 3 | const pascalCase = (string) => upperFirst(camelCase(string)) 4 | 5 | module.exports.pascalCase = pascalCase 6 | -------------------------------------------------------------------------------- /scripts/generate-routes/set-url.js: -------------------------------------------------------------------------------- 1 | const URL_CORRECTION = {} 2 | 3 | const setUrl = (endpointObject, url) => { 4 | endpointObject.url = URL_CORRECTION[url] || url 5 | } 6 | 7 | module.exports.setUrl = setUrl 8 | -------------------------------------------------------------------------------- /src/endpoint/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "basePath": "/2.0", 3 | "consumes": ["application/json"], 4 | "host": "api.bitbucket.org", 5 | "produces": ["application/json"], 6 | "schemes": ["https"], 7 | "swagger": "2.0" 8 | } 9 | -------------------------------------------------------------------------------- /src/plugins/notice/types.ts: -------------------------------------------------------------------------------- 1 | export type APIClient = import('../../client/types').APIClient 2 | export type Options = import('../../client/types').Options 3 | export type RequestOptions = import('../../endpoint/types').RequestOptions 4 | -------------------------------------------------------------------------------- /templates/minimal.d.ts.mustache: -------------------------------------------------------------------------------- 1 | import { APIClient as _APIClient, APIClientFactory } from './bitbucket' 2 | 3 | export interface APIClient extends _APIClient {} 4 | 5 | export declare const Bitbucket: APIClientFactory 6 | -------------------------------------------------------------------------------- /scripts/generate-routes/process-deprecated.js: -------------------------------------------------------------------------------- 1 | const processDeprecated = (endpointObject, { deprecated = false } = {}) => { 2 | if (deprecated) endpointObject.deprecated = true 3 | } 4 | 5 | module.exports.processDeprecated = processDeprecated 6 | -------------------------------------------------------------------------------- /src/utils/btoa-lite.ts: -------------------------------------------------------------------------------- 1 | const btoaLite = (data: string): string => { 2 | if (typeof Buffer !== 'undefined') { 3 | return Buffer.from(data).toString('base64') 4 | } 5 | 6 | return btoa(data) 7 | } 8 | 9 | export default btoaLite 10 | -------------------------------------------------------------------------------- /apidoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bitbucket API client", 3 | "title": "Bitbucket API client Docs", 4 | "header": { 5 | "title": "Introduction", 6 | "filename": "README.md" 7 | }, 8 | "template": { 9 | "withCompare": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/plugins/_oauth/index.js: -------------------------------------------------------------------------------- 1 | const oAuth2Spec = require('./spec.json') 2 | 3 | const routes = require('./routes.js')(oAuth2Spec) 4 | 5 | function oAuthPlugin(client) { 6 | client.registerEndpoints({ oauth: routes }) 7 | } 8 | 9 | module.exports = oAuthPlugin 10 | -------------------------------------------------------------------------------- /src/plugins/register-api-endpoints/index.ts: -------------------------------------------------------------------------------- 1 | import ROUTES from './routes.json' 2 | 3 | type APIClient = import('./types').APIClient 4 | 5 | function registerApiEndpointsPlugin(client: APIClient): void { 6 | client.registerEndpoints(ROUTES) 7 | } 8 | 9 | export default registerApiEndpointsPlugin 10 | -------------------------------------------------------------------------------- /src/plugins/validate-request/index.ts: -------------------------------------------------------------------------------- 1 | import { validate } from './validate' 2 | 3 | type APIClient = import('./types').APIClient 4 | 5 | function validateRequestPlugin(client: APIClient): void { 6 | client.requestHook.before(validate.bind(null, client)) 7 | } 8 | 9 | export default validateRequestPlugin 10 | -------------------------------------------------------------------------------- /src/plugins/pagination/types.ts: -------------------------------------------------------------------------------- 1 | export type APIClient = import('../../client/types').APIClient 2 | export type Response = import('../../request/types').Response 3 | export type PaginatedResponseData< 4 | T 5 | > = import('../../request/types').PaginatedResponseData 6 | 7 | export type Direction = 'next' | 'previous' 8 | -------------------------------------------------------------------------------- /scripts/generate-routes/process-produces.js: -------------------------------------------------------------------------------- 1 | const { set } = require('lodash') 2 | 3 | const processProduces = (endpointObject, { produces = [] } = {}) => { 4 | if (produces.length === 1) { 5 | set(endpointObject, 'headers.accept', produces[0]) 6 | } 7 | } 8 | 9 | module.exports.processProduces = processProduces 10 | -------------------------------------------------------------------------------- /scripts/utils/get-duplicates.js: -------------------------------------------------------------------------------- 1 | const { chain, includes } = require('lodash') 2 | 3 | const getDuplicates = (array) => { 4 | return chain(array) 5 | .filter((value, index, iteratee) => includes(iteratee, value, index + 1)) 6 | .uniq() 7 | .value() 8 | } 9 | 10 | module.exports.getDuplicates = getDuplicates 11 | -------------------------------------------------------------------------------- /src/client/utils/pick.ts: -------------------------------------------------------------------------------- 1 | export function pick(object: T, keys: K[]): Pick { 2 | return keys.reduce((newObject: Partial, key): Partial => { 3 | if (typeof object[key] !== 'undefined') { 4 | newObject[key] = object[key] 5 | } 6 | return newObject 7 | }, {}) as Pick 8 | } 9 | -------------------------------------------------------------------------------- /src/plugins/auth/validate-options.ts: -------------------------------------------------------------------------------- 1 | type AuthOptions = import('./types').AuthOptions 2 | 3 | export function validateOptions(auth: AuthOptions): void { 4 | if ('token' in auth) return 5 | 6 | if (auth.username && auth.password) return 7 | 8 | throw new Error(`Invalid "auth" option: ${JSON.stringify(auth)}`) 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/lower-case-header-fields.ts: -------------------------------------------------------------------------------- 1 | type Headers = import('../endpoint/types').Headers 2 | 3 | export function lowerCaseHeaderFields(headers: Headers = {}): Headers { 4 | return Object.keys(headers).reduce((newHeaders: Headers, key): Headers => { 5 | newHeaders[key.toLowerCase()] = headers[key] 6 | return newHeaders 7 | }, {}) 8 | } 9 | -------------------------------------------------------------------------------- /src/plugins/register-endpoints/index.ts: -------------------------------------------------------------------------------- 1 | import { registerEndpoints } from './register-endpoints' 2 | 3 | type APIClient = import('./types').APIClient 4 | 5 | function registerEndpointsPlugin(client: APIClient): void { 6 | client.registerEndpoints = registerEndpoints.bind(null, client) 7 | } 8 | 9 | export default registerEndpointsPlugin 10 | -------------------------------------------------------------------------------- /templates/index.d.ts.mustache: -------------------------------------------------------------------------------- 1 | import { 2 | APIClient as _APIClient, 3 | APIClientFactory, 4 | APIEndpoints 5 | } from './bitbucket' 6 | 7 | export { APIEndpoints, Params, Schema } from './bitbucket' 8 | 9 | export interface APIClient extends _APIClient, APIEndpoints {} 10 | 11 | export declare const Bitbucket: APIClientFactory 12 | -------------------------------------------------------------------------------- /scripts/utils/extract-namespace-names.js: -------------------------------------------------------------------------------- 1 | const { chain, keys, values } = require('lodash') 2 | 3 | const extractNamespaceNames = (endpointNames) => { 4 | return chain(endpointNames) 5 | .values() 6 | .flatMap(values) 7 | .flatMap(keys) 8 | .uniq() 9 | .value() 10 | } 11 | 12 | module.exports.extractNamespaceNames = extractNamespaceNames 13 | -------------------------------------------------------------------------------- /scripts/generate-routes/process-consumes.js: -------------------------------------------------------------------------------- 1 | const { uniq } = require('lodash') 2 | 3 | const processConsumes = (endpointObject, { consumes = [] } = {}) => { 4 | if (!endpointObject.accepts) endpointObject.accepts = [] 5 | 6 | endpointObject.accepts = uniq(endpointObject.accepts.concat(consumes)) 7 | } 8 | 9 | module.exports.processConsumes = processConsumes 10 | -------------------------------------------------------------------------------- /src/error/types.ts: -------------------------------------------------------------------------------- 1 | export type Headers = import('../endpoint/types').Headers 2 | export type RequestOptions = import('../endpoint/types').RequestOptions 3 | 4 | export abstract class HTTPError extends Error { 5 | public error!: any | undefined 6 | public headers!: Headers | undefined 7 | public request!: RequestOptions | undefined 8 | public status!: number 9 | } 10 | -------------------------------------------------------------------------------- /scripts/utils/is-paginated-endpoint.js: -------------------------------------------------------------------------------- 1 | const isPaginatedEndpoint = (endpointName, endpointObject) => { 2 | const isPaginatedList = /^list/i.test(endpointName) 3 | const returnsPaginated = 4 | endpointObject.returns && /^paginated/i.test(endpointObject.returns) 5 | 6 | return isPaginatedList || returnsPaginated 7 | } 8 | 9 | module.exports.isPaginatedEndpoint = isPaginatedEndpoint 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "baseUrl": "./src", 6 | "strict": true, 7 | "allowSyntheticDefaultImports": true, 8 | "esModuleInterop": true, 9 | "moduleResolution": "node", 10 | "resolveJsonModule": true, 11 | "sourceMap": true, 12 | "noEmit": true 13 | }, 14 | "include": ["src"] 15 | } 16 | -------------------------------------------------------------------------------- /templates/plugins/authenticate.d.ts.mustache: -------------------------------------------------------------------------------- 1 | import { AuthenticateOptions } from '../../src/plugins/authenticate/types' 2 | 3 | declare module 'bitbucket' { 4 | export interface APIClient { 5 | authenticate(options: AuthenticateOptions): void 6 | } 7 | } 8 | 9 | declare module 'bitbucket/lib/minimal' { 10 | export interface APIClient { 11 | authenticate(options: AuthenticateOptions): void 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/endpoint/defaults.ts: -------------------------------------------------------------------------------- 1 | import { version } from '../../package.json' 2 | import { basePath, host, schemes } from './spec.json' 3 | 4 | type EndpointDefaults = import('./types').EndpointDefaults 5 | 6 | export const DEFAULTS: EndpointDefaults = { 7 | method: `GET`, 8 | baseUrl: `${schemes[0]}://${host}${basePath}`, 9 | headers: { 10 | accept: `application/json`, 11 | 'user-agent': `bitbucket.js/${version}`, 12 | }, 13 | } 14 | -------------------------------------------------------------------------------- /scripts/utils/write-to-file.js: -------------------------------------------------------------------------------- 1 | const { writeFileSync } = require('fs') 2 | const prettier = require('prettier') 3 | 4 | const writeToFile = async (filePath, fileContent) => { 5 | const options = await prettier.resolveConfig(filePath) 6 | 7 | const formattedContent = prettier.format(fileContent, { 8 | ...options, 9 | filepath: filePath, 10 | }) 11 | 12 | writeFileSync(filePath, formattedContent) 13 | } 14 | 15 | module.exports.writeToFile = writeToFile 16 | -------------------------------------------------------------------------------- /src/client/factory.ts: -------------------------------------------------------------------------------- 1 | import { constructor } from './constructor' 2 | import { registerPlugins } from './register-plugins' 3 | 4 | type APIClientFactory = import('./types').APIClientFactory 5 | type Plugin = import('./types').Plugin 6 | 7 | export function factory(plugins: Plugin[] = []): APIClientFactory { 8 | const Bitbucket = constructor.bind(null, plugins) as APIClientFactory 9 | Bitbucket.plugins = registerPlugins.bind(null, plugins) 10 | return Bitbucket 11 | } 12 | -------------------------------------------------------------------------------- /scripts/update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "Generating specification..." 4 | yarn run generate:specification 5 | 6 | echo "Generating endpoint names..." 7 | yarn run generate:endpoint-names 8 | 9 | echo "Press any key to review endpoint names..." 10 | read -n 1 11 | ${EDITOR:-vim} specification/extras/endpoint-names.json 12 | 13 | echo "Generating routes..." 14 | yarn run generate:routes 15 | 16 | echo "Generating types schema..." 17 | yarn run generate:types-schema 18 | -------------------------------------------------------------------------------- /scripts/utils/extract-endpoint-names-for-namespace.js: -------------------------------------------------------------------------------- 1 | const { chain, isEmpty, pick, values } = require('lodash') 2 | 3 | const extractEndpointNamesForNamespace = (endpointNames, namespaceName) => { 4 | return chain(endpointNames) 5 | .values() 6 | .flatMap(values) 7 | .flatMap((object) => pick(object, namespaceName)) 8 | .reject(isEmpty) 9 | .flatMap(values) 10 | .compact() 11 | .value() 12 | } 13 | 14 | module.exports.extractEndpointNamesForNamespace = extractEndpointNamesForNamespace 15 | -------------------------------------------------------------------------------- /scripts/generate-routes/set-alias.js: -------------------------------------------------------------------------------- 1 | const setAlias = ( 2 | endpointObject, 3 | namespaceName, 4 | resourceNamespaceName, 5 | endpointNameByNamespaces 6 | ) => { 7 | const isAlias = namespaceName !== resourceNamespaceName 8 | const originalEndpointName = endpointNameByNamespaces[resourceNamespaceName] 9 | 10 | if (isAlias && originalEndpointName) { 11 | endpointObject.alias = `${resourceNamespaceName}.${originalEndpointName}` 12 | } 13 | 14 | return isAlias 15 | } 16 | 17 | module.exports.setAlias = setAlias 18 | -------------------------------------------------------------------------------- /src/client/register-plugins.ts: -------------------------------------------------------------------------------- 1 | import { factory } from './factory' 2 | 3 | type APIClientFactory = import('./types').APIClientFactory 4 | type Plugin = import('./types').Plugin 5 | 6 | export function registerPlugins( 7 | oldPlugins: Plugin[] = [], 8 | newPlugins: Plugin[] = [] 9 | ): APIClientFactory { 10 | const plugins = oldPlugins.slice(0) 11 | 12 | newPlugins.forEach((plugin): void => { 13 | if (!plugins.includes(plugin)) { 14 | plugins.push(plugin) 15 | } 16 | }) 17 | 18 | return factory(plugins) 19 | } 20 | -------------------------------------------------------------------------------- /src/endpoint/utils/extract-url-variable-names.ts: -------------------------------------------------------------------------------- 1 | const urlVariableRegex = /\{[^}]+\}/g 2 | 3 | function removeNonChars(variableNameMatch: string): string[] { 4 | return variableNameMatch.replace(/^\W+|\W+$/g, '').split(/,/) 5 | } 6 | 7 | export function extractUrlVariableNames(url: string): string[] { 8 | const matches = url.match(urlVariableRegex) 9 | 10 | if (!matches) { 11 | return [] 12 | } 13 | 14 | return matches 15 | .map(removeNonChars) 16 | .reduce((a: string[], b): string[] => a.concat(b), []) 17 | } 18 | -------------------------------------------------------------------------------- /src/plugins/auth/types.ts: -------------------------------------------------------------------------------- 1 | export type APIClient = import('../../client/types').APIClient 2 | export type Options = import('../../client/types').Options 3 | export type RequestOptions = import('../../endpoint/types').RequestOptions 4 | 5 | export type AuthBasic = { 6 | username: string 7 | password: string 8 | } 9 | 10 | export type AuthToken = { 11 | token: string 12 | } 13 | 14 | export type AuthOptions = AuthBasic | AuthToken 15 | 16 | export type AuthPluginState = { 17 | client: APIClient 18 | auth: AuthOptions 19 | } 20 | -------------------------------------------------------------------------------- /src/minimal.ts: -------------------------------------------------------------------------------- 1 | import { Client } from './client' 2 | import authPlugin from './plugins/auth' 3 | import noticePlugin from './plugins/notice' 4 | import paginationPlugin from './plugins/pagination' 5 | import registerEndpointsPlugin from './plugins/register-endpoints' 6 | import validateRequestPlugin from './plugins/validate-request' 7 | 8 | const Plugins = [ 9 | noticePlugin, 10 | authPlugin, 11 | paginationPlugin, 12 | registerEndpointsPlugin, 13 | validateRequestPlugin, 14 | ] 15 | 16 | export const Bitbucket = Client.plugins(Plugins) 17 | -------------------------------------------------------------------------------- /src/plugins/notice/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | type APIClient = import('./types').APIClient 4 | type Options = import('./types').Options 5 | 6 | function noticePlugin(_client: APIClient, clientOptions: Options): void { 7 | const { notice = true } = clientOptions 8 | 9 | if (!notice) return 10 | 11 | console.log( 12 | '\x1b[46m\x1b[30m %s \x1b[0m\x1b[36m %s \x1b[0m', 13 | `BITBUCKET CLOUD API LATEST UPDATES:`, 14 | `https://developer.atlassian.com/cloud/bitbucket` 15 | ) 16 | } 17 | 18 | export default noticePlugin 19 | -------------------------------------------------------------------------------- /src/plugins/authenticate/types.ts: -------------------------------------------------------------------------------- 1 | type AuthBasic = import('../auth/types').AuthBasic 2 | type AuthToken = import('../auth/types').AuthToken 3 | export type APIClient = import('../../client/types').APIClient 4 | export type Options = import('../../client/types').Options 5 | export type RequestOptions = import('../../endpoint/types').RequestOptions 6 | 7 | export type AuthenticateOptions = 8 | | (AuthBasic & { type: 'apppassword' | 'basic' }) 9 | | (AuthToken & { type: 'token' }) 10 | 11 | export type AuthenticatePluginState = { 12 | client: APIClient 13 | auth?: AuthenticateOptions 14 | } 15 | -------------------------------------------------------------------------------- /src/plugins/auth/before-request.ts: -------------------------------------------------------------------------------- 1 | import btoaLite from 'utils/btoa-lite' 2 | 3 | type AuthPluginState = import('./types').AuthPluginState 4 | type RequestOptions = import('./types').RequestOptions 5 | 6 | export function beforeRequest( 7 | state: AuthPluginState, 8 | requestOptions: RequestOptions 9 | ): void { 10 | if ('token' in state.auth) { 11 | requestOptions.headers.authorization = `Bearer ${state.auth.token}` 12 | } else if (state.auth.username) { 13 | const hash = btoaLite(`${state.auth.username}:${state.auth.password}`) 14 | 15 | requestOptions.headers.authorization = `Basic ${hash}` 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/endpoint/endpoint-with-defaults.ts: -------------------------------------------------------------------------------- 1 | import { merge } from './merge' 2 | import { parse } from './parse' 3 | 4 | type EndpointDefaults = import('./types').EndpointDefaults 5 | type EndpointOptions = import('./types').EndpointOptions 6 | type EndpointParams = import('./types').EndpointParams 7 | type RequestOptions = import('./types').RequestOptions 8 | 9 | export function endpointWithDefaults( 10 | endpointDefaults: EndpointDefaults, 11 | endpointRoute: string | EndpointOptions, 12 | endpointOptions?: EndpointParams 13 | ): RequestOptions { 14 | return parse(merge(endpointDefaults, endpointRoute, endpointOptions)) 15 | } 16 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Client } from './client' 2 | import authPlugin from './plugins/auth' 3 | import noticePlugin from './plugins/notice' 4 | import paginationPlugin from './plugins/pagination' 5 | import registerApiEndpointsPlugin from './plugins/register-api-endpoints' 6 | import registerEndpointsPlugin from './plugins/register-endpoints' 7 | import validateRequestPlugin from './plugins/validate-request' 8 | 9 | const Plugins = [ 10 | noticePlugin, 11 | authPlugin, 12 | paginationPlugin, 13 | registerEndpointsPlugin, 14 | registerApiEndpointsPlugin, 15 | validateRequestPlugin, 16 | ] 17 | 18 | export const Bitbucket = Client.plugins(Plugins) 19 | -------------------------------------------------------------------------------- /src/plugins/pagination/index.ts: -------------------------------------------------------------------------------- 1 | import { getPage } from './get-page' 2 | 3 | type APIClient = import('./types').APIClient 4 | type PaginatedResponseData = import('./types').PaginatedResponseData 5 | 6 | function paginationPlugin(client: APIClient): void { 7 | client.hasNextPage = ({ next }: PaginatedResponseData): boolean => 8 | Boolean(next) 9 | client.getNextPage = getPage.bind(null, client, 'next') 10 | 11 | client.hasPreviousPage = ({ 12 | previous, 13 | }: PaginatedResponseData): boolean => Boolean(previous) 14 | client.getPreviousPage = getPage.bind(null, client, 'previous') 15 | } 16 | 17 | export default paginationPlugin 18 | -------------------------------------------------------------------------------- /src/plugins/auth/index.ts: -------------------------------------------------------------------------------- 1 | import { beforeRequest } from './before-request.js' 2 | import { validateOptions } from './validate-options' 3 | 4 | type APIClient = import('./types').APIClient 5 | type AuthPluginState = import('./types').AuthPluginState 6 | type Options = import('./types').Options 7 | 8 | function authPlugin(client: APIClient, clientOptions: Options): void { 9 | if (!clientOptions.auth) return 10 | 11 | validateOptions(clientOptions.auth) 12 | 13 | const state: AuthPluginState = { 14 | client, 15 | auth: clientOptions.auth, 16 | } 17 | 18 | client.requestHook.before(beforeRequest.bind(null, state)) 19 | } 20 | 21 | export default authPlugin 22 | -------------------------------------------------------------------------------- /src/plugins/pagination/get-page.ts: -------------------------------------------------------------------------------- 1 | import { HTTPError } from '../../error' 2 | 3 | type APIClient = import('./types').APIClient 4 | type Direction = import('./types').Direction 5 | type PaginatedResponseData = import('./types').PaginatedResponseData 6 | type Response = import('./types').Response 7 | 8 | export function getPage( 9 | client: APIClient, 10 | direction: Direction, 11 | responseData: PaginatedResponseData 12 | ): Promise> { 13 | const url = responseData[direction] 14 | 15 | if (!url) { 16 | throw new HTTPError(`not found: ${direction} page`, 404) 17 | } 18 | 19 | return client.request({ method: 'GET', url }) 20 | } 21 | -------------------------------------------------------------------------------- /src/plugins/authenticate/index.ts: -------------------------------------------------------------------------------- 1 | import { authenticate } from './authenticate' 2 | import { beforeRequest } from './before-request' 3 | 4 | type APIClient = import('./types').APIClient 5 | type AuthenticatePluginState = import('./types').AuthenticatePluginState 6 | type Options = import('./types').Options 7 | 8 | function authenticatePlugin(client: APIClient, clientOptions: Options): void { 9 | if (clientOptions.auth) return 10 | 11 | const state: AuthenticatePluginState = { 12 | client, 13 | auth: undefined, 14 | } 15 | 16 | client.authenticate = authenticate.bind(null, state) 17 | client.requestHook.before(beforeRequest.bind(null, state)) 18 | } 19 | 20 | export default authenticatePlugin 21 | -------------------------------------------------------------------------------- /scripts/generate-routes/process-responses.js: -------------------------------------------------------------------------------- 1 | const { pascalCase } = require('../utils/pascal-case') 2 | 3 | const processResponses = (endpointObject, { responses = {} } = {}) => { 4 | Object.entries(responses).forEach(([statusCode, responseObject]) => { 5 | if (Number(statusCode) < 300 && responseObject.schema) { 6 | const isArraySchema = responseObject.schema.type === 'array' 7 | 8 | const ref = isArraySchema 9 | ? responseObject.schema.items.$ref 10 | : responseObject.schema.$ref 11 | 12 | const returns = pascalCase(ref.replace('#/definitions/', '')) 13 | 14 | endpointObject.returns = isArraySchema ? `${returns}[]` : returns 15 | } 16 | }) 17 | } 18 | 19 | module.exports.processResponses = processResponses 20 | -------------------------------------------------------------------------------- /src/plugins/authenticate/before-request.ts: -------------------------------------------------------------------------------- 1 | import btoaLite from 'utils/btoa-lite' 2 | 3 | type AuthenticatePluginState = import('./types').AuthenticatePluginState 4 | type RequestOptions = import('./types').RequestOptions 5 | 6 | export function beforeRequest( 7 | state: AuthenticatePluginState, 8 | requestOptions: RequestOptions 9 | ): void { 10 | if (!state.auth) { 11 | return 12 | } 13 | 14 | switch (state.auth.type) { 15 | case 'apppassword': 16 | case 'basic': 17 | requestOptions.headers.authorization = `Basic ${btoaLite( 18 | `${state.auth.username}:${state.auth.password}` 19 | )}` 20 | break 21 | case 'token': 22 | requestOptions.headers.authorization = `Bearer ${state.auth.token}` 23 | break 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/client/constructor.ts: -------------------------------------------------------------------------------- 1 | import { Singular } from 'before-after-hook' 2 | import { request } from '../request' 3 | import { getEndpointOptions } from './get-endpoint-options' 4 | 5 | type APIClient = import('./types').APIClient 6 | type Options = import('./types').Options 7 | type Plugin = import('./types').Plugin 8 | type RequestHook = import('./types').RequestHook 9 | 10 | export function constructor( 11 | plugins: Plugin[], 12 | clientOptions: Options = {} 13 | ): APIClient { 14 | const requestHook: RequestHook = new Singular() 15 | 16 | const client = { 17 | request: request.defaults(getEndpointOptions(clientOptions, requestHook)), 18 | requestHook, 19 | } 20 | 21 | plugins.forEach((plugin): void => { 22 | plugin(client, clientOptions) 23 | }) 24 | 25 | return client 26 | } 27 | -------------------------------------------------------------------------------- /src/endpoint/with-defaults.ts: -------------------------------------------------------------------------------- 1 | import { endpointWithDefaults } from './endpoint-with-defaults' 2 | import { merge } from './merge' 3 | import { parse } from './parse' 4 | 5 | type Endpoint = import('./types').Endpoint 6 | type EndpointDefaults = import('./types').EndpointDefaults 7 | type EndpointParams = import('./types').EndpointParams 8 | 9 | export function withDefaults( 10 | oldDefaults: EndpointDefaults | null, 11 | newDefaults: EndpointParams 12 | ): Endpoint { 13 | const DEFAULTS = merge(oldDefaults, newDefaults) 14 | const endpoint = endpointWithDefaults.bind(null, DEFAULTS) as Endpoint 15 | 16 | endpoint.DEFAULTS = DEFAULTS 17 | endpoint.defaults = withDefaults.bind(null, DEFAULTS) 18 | endpoint.merge = merge.bind(null, DEFAULTS) 19 | endpoint.parse = parse 20 | 21 | return endpoint 22 | } 23 | -------------------------------------------------------------------------------- /.bilirc.ts: -------------------------------------------------------------------------------- 1 | import { Config } from 'bili' 2 | 3 | const config: Config = { 4 | input: ['src/index.ts', 'src/minimal.ts'], 5 | output: { 6 | dir: 'lib', 7 | fileName: ({ format }) => { 8 | return ['es', 'esm'].includes(format) 9 | ? `[name][min][ext]` 10 | : `[name][min].[format][ext]` 11 | }, 12 | format: ['es', 'umd'], 13 | minify: true, 14 | moduleName: 'Bitbucket', 15 | sourceMapExcludeSources: true, 16 | }, 17 | globals: { 18 | 'node-fetch': 'fetch', 19 | }, 20 | } 21 | 22 | const PLUGIN = process.env.PLUGIN 23 | 24 | if (PLUGIN) { 25 | config.input = `src/plugins/${PLUGIN}/index.ts` 26 | config.output.dir = 'lib/plugins' 27 | config.output.format = 'umd' 28 | config.output.moduleName = `Bitbucket.Plugin.${PLUGIN}` 29 | config.output.fileName = `${PLUGIN}[min][ext]` 30 | } 31 | 32 | export default config 33 | -------------------------------------------------------------------------------- /src/client/get-endpoint-options.ts: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge' 2 | import isPlainObject from 'is-plain-object' 3 | import { lowerCaseHeaderFields } from '../utils/lower-case-header-fields' 4 | import { pick } from './utils/pick' 5 | 6 | type EndpointParams = import('./types').EndpointParams 7 | type Options = import('./types').Options 8 | type RequestHook = import('./types').RequestHook 9 | 10 | export function getEndpointOptions( 11 | clientOptions: Options, 12 | requestHook: RequestHook 13 | ): EndpointParams { 14 | clientOptions.headers = lowerCaseHeaderFields(clientOptions.headers) 15 | 16 | const endpointOptions = deepmerge( 17 | { headers: {}, request: {} }, 18 | pick(clientOptions, ['baseUrl', 'headers', 'request']), 19 | { isMergeableObject: isPlainObject } 20 | ) 21 | 22 | endpointOptions.request.hook = requestHook 23 | 24 | return endpointOptions 25 | } 26 | -------------------------------------------------------------------------------- /src/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'standard-with-typescript', 4 | 'plugin:prettier/recommended', 5 | 'prettier/standard', 6 | 'prettier/@typescript-eslint', 7 | ], 8 | parser: '@typescript-eslint/parser', 9 | parserOptions: { 10 | project: './tsconfig.json', 11 | createDefaultProgram: true, 12 | }, 13 | rules: { 14 | '@typescript-eslint/camelcase': 'warn', 15 | '@typescript-eslint/consistent-type-definitions': 'off', 16 | '@typescript-eslint/no-empty-interface': 'warn', 17 | '@typescript-eslint/no-non-null-assertion': 'warn', 18 | '@typescript-eslint/prefer-interface': 0, 19 | '@typescript-eslint/promise-function-async': 'off', 20 | '@typescript-eslint/require-array-sort-compare': 'off', 21 | '@typescript-eslint/require-await': 'warn', 22 | '@typescript-eslint/strict-boolean-expressions': 'off', 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /src/endpoint/utils/add-query-parameters.ts: -------------------------------------------------------------------------------- 1 | export function addQueryParameters( 2 | url: string, 3 | params: { [param: string]: any; q?: string } = {} 4 | ): string { 5 | const separator = url.includes('?') ? '&' : '?' 6 | const names = Object.keys(params).filter( 7 | (name) => typeof params[name] !== 'undefined' 8 | ) 9 | 10 | if (names.length === 0) { 11 | return url 12 | } 13 | 14 | return `${url}${separator}${names 15 | .map((name): string => { 16 | if (name === 'q') { 17 | return `q=${params.q!.split(' ').map(encodeURIComponent).join('+')}` 18 | } 19 | 20 | if (Array.isArray(params[name])) { 21 | return params[name] 22 | .map((value: string) => `${name}=${encodeURIComponent(value)}`) 23 | .join('&') 24 | } 25 | 26 | return `${name}=${encodeURIComponent(params[name])}` 27 | }) 28 | .join('&')}` 29 | } 30 | -------------------------------------------------------------------------------- /src/plugins/register-endpoints/types.ts: -------------------------------------------------------------------------------- 1 | export type APIClient = import('../../client/types').APIClient 2 | export type Options = import('../../client/types').Options 3 | export type EndpointDefaults = import('../../endpoint/types').EndpointDefaults 4 | export type Headers = import('../../endpoint/types').Headers 5 | export type RequestMethod = import('../../endpoint/types').RequestMethod 6 | export type RequestOptions = import('../../endpoint/types').RequestOptions 7 | 8 | export type Routes = { 9 | [namespace: string]: { 10 | [endpoint: string]: { 11 | accepts?: string[] 12 | alias?: string 13 | deprecated?: boolean 14 | headers?: Headers 15 | method?: RequestMethod 16 | params?: { 17 | [param: string]: { 18 | enum?: string[] 19 | required?: boolean 20 | schema?: string 21 | type: string 22 | } 23 | } 24 | returns?: string 25 | url?: string 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/error/index.ts: -------------------------------------------------------------------------------- 1 | type Headers = import('./types').Headers 2 | type RequestOptions = import('./types').RequestOptions 3 | 4 | type AbstractHTTPError = import('./types').HTTPError 5 | 6 | export class HTTPError extends Error implements AbstractHTTPError { 7 | public error: any | undefined 8 | public headers: Headers | undefined 9 | public request: RequestOptions | undefined 10 | public status: number 11 | 12 | public constructor( 13 | message: string, 14 | statusCode: number, 15 | options: { 16 | error?: any 17 | headers?: Headers 18 | request?: RequestOptions 19 | } = {} 20 | ) { 21 | super(message) 22 | 23 | // Maintains proper stack trace (only available on V8) 24 | if (Error.captureStackTrace) Error.captureStackTrace(this, this.constructor) 25 | 26 | this.name = 'HTTPError' 27 | this.error = options.error 28 | this.headers = options.headers 29 | this.request = options.request 30 | this.status = statusCode 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/request/types.ts: -------------------------------------------------------------------------------- 1 | export type Endpoint = import('../endpoint/types').Endpoint 2 | export type EndpointDefaults = import('../endpoint/types').EndpointDefaults 3 | export type EndpointOptions = import('../endpoint/types').EndpointOptions 4 | export type EndpointParams = import('../endpoint/types').EndpointParams 5 | export type Headers = import('../endpoint/types').Headers 6 | 7 | export interface Response { 8 | data: T 9 | headers: Headers & { 10 | date?: string 11 | etag?: string 12 | 'x-accepted-oauth-scopes'?: string 13 | } 14 | status: number 15 | url: string 16 | } 17 | 18 | export type PaginatedResponseData = Response['data'] & { 19 | next?: string 20 | previous?: string 21 | } 22 | 23 | export interface Request { 24 | (endpointRoute: string, endpointOptions?: EndpointParams): Promise< 25 | Response 26 | > 27 | (endpointOptions: EndpointOptions): Promise> 28 | defaults(endpointOptions: EndpointParams): Request 29 | endpoint: Endpoint 30 | } 31 | -------------------------------------------------------------------------------- /src/client/types.ts: -------------------------------------------------------------------------------- 1 | type HookSingular = import('before-after-hook').HookSingular 2 | type RequestOptions = import('../endpoint/types').RequestOptions 3 | export type EndpointParams = import('../endpoint/types').EndpointParams 4 | type HTTPError = import('../error/types').HTTPError 5 | type Request = import('../request/types').Request 6 | type Response = import('../request/types').Response 7 | 8 | export interface Options { 9 | [option: string]: any 10 | baseUrl?: string 11 | request?: RequestOptions['request'] 12 | } 13 | 14 | export type RequestHook = HookSingular, HTTPError> 15 | 16 | export interface APIClient { 17 | [key: string]: any 18 | request: Request 19 | requestHook: RequestHook 20 | } 21 | 22 | export type Plugin = (client: APIClient, options: Options) => void 23 | 24 | export interface APIClientFactory { 25 | new (options?: Options): APIClient 26 | (options?: Options): APIClient 27 | 28 | plugins(plugins: Plugin[]): APIClientFactory 29 | } 30 | -------------------------------------------------------------------------------- /src/plugins/authenticate/authenticate.ts: -------------------------------------------------------------------------------- 1 | type AuthenticatePluginState = import('./types').AuthenticatePluginState 2 | type AuthenticateOptions = import('./types').AuthenticateOptions 3 | 4 | export function authenticate( 5 | state: AuthenticatePluginState, 6 | options: AuthenticateOptions 7 | ): void { 8 | if (!options) { 9 | delete state.auth 10 | return 11 | } 12 | 13 | switch (options.type) { 14 | case 'apppassword': 15 | case 'basic': 16 | if (!options.username || !options.password) { 17 | throw new Error( 18 | 'Basic authentication requires both a username and password to be set' 19 | ) 20 | } 21 | break 22 | 23 | case 'token': 24 | if (!options.token) { 25 | throw new Error('Token authentication requires a token to be set') 26 | } 27 | break 28 | 29 | default: 30 | throw new Error( 31 | "Invalid authentication type, must be 'apppassword', 'basic' or 'token'" 32 | ) 33 | } 34 | 35 | state.auth = options 36 | } 37 | -------------------------------------------------------------------------------- /src/endpoint/merge.ts: -------------------------------------------------------------------------------- 1 | import deepmerge from 'deepmerge' 2 | import isPlainObject from 'is-plain-object' 3 | import { lowerCaseHeaderFields } from '../utils/lower-case-header-fields' 4 | 5 | type EndpointDefaults = import('./types').EndpointDefaults 6 | type EndpointParams = import('./types').EndpointParams 7 | 8 | export function merge( 9 | endpointDefaults: EndpointDefaults | null, 10 | endpointRoute: string | EndpointParams, 11 | endpointOptions?: EndpointParams 12 | ): EndpointDefaults { 13 | if (typeof endpointRoute === 'string') { 14 | const [method, url] = endpointRoute.split(' ') 15 | endpointOptions = Object.assign( 16 | url ? { method, url } : { url: method }, 17 | endpointOptions 18 | ) 19 | } else { 20 | endpointOptions = endpointRoute || {} 21 | } 22 | 23 | endpointOptions.headers = lowerCaseHeaderFields(endpointOptions.headers) 24 | 25 | const mergedOptions = deepmerge.all( 26 | [endpointDefaults!, endpointOptions].filter(Boolean), 27 | { isMergeableObject: isPlainObject } 28 | ) as EndpointDefaults 29 | 30 | return mergedOptions 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Munif Tanjim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/request/with-defaults.ts: -------------------------------------------------------------------------------- 1 | import { fetchWrapper } from './fetch-wrapper' 2 | 3 | type Endpoint = import('./types').Endpoint 4 | type EndpointDefaults = import('./types').EndpointDefaults 5 | type EndpointOptions = import('./types').EndpointOptions 6 | type EndpointParams = import('./types').EndpointParams 7 | type Request = import('./types').Request 8 | 9 | export function withDefaults( 10 | oldEndpoint: Endpoint, 11 | newDefaults: EndpointParams 12 | ): Request { 13 | const endpoint = oldEndpoint.defaults(newDefaults) 14 | 15 | function request( 16 | endpointRoute: string | EndpointOptions, 17 | endpointParams?: EndpointParams 18 | ): ReturnType { 19 | const endpointOptions = endpoint.merge( 20 | endpointRoute as string, 21 | endpointParams 22 | ) 23 | 24 | if (!endpointOptions.request || !endpointOptions.request.hook) { 25 | return fetchWrapper(endpoint.parse(endpointOptions)) 26 | } 27 | 28 | return endpointOptions.request.hook( 29 | (options: EndpointDefaults): ReturnType => 30 | fetchWrapper(endpoint.parse(options)), 31 | endpointOptions 32 | ) 33 | } 34 | 35 | request.defaults = withDefaults.bind(null, endpoint) 36 | request.endpoint = endpoint 37 | 38 | return request 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # type definitions are generated before publish 64 | **/*.d.ts 65 | 66 | # Others 67 | .rpt2_cache 68 | docs 69 | lib 70 | /bitbucket-test.js 71 | 72 | # Mac 73 | .DS_Store 74 | 75 | # VSCode 76 | .history 77 | .VSCode 78 | -------------------------------------------------------------------------------- /src/endpoint/types.ts: -------------------------------------------------------------------------------- 1 | export type Headers = { [header: string]: string } 2 | 3 | export type RequestMethod = 'DELETE' | 'GET' | 'POST' | 'PUT' 4 | 5 | export type EndpointParams = { 6 | baseUrl?: string 7 | headers?: Headers 8 | request?: { [option: string]: any } 9 | [param: string]: any 10 | } 11 | 12 | export type EndpointDefaults = EndpointParams & { 13 | method: RequestMethod 14 | baseUrl: string 15 | headers: { 16 | accept: string 17 | 'user-agent': string 18 | } 19 | } 20 | 21 | export type EndpointOptions = EndpointParams & { 22 | method: RequestMethod 23 | url: string 24 | } 25 | 26 | export type RequestOptions = { 27 | method: RequestMethod 28 | url: string 29 | body?: any 30 | headers: EndpointDefaults['headers'] & { 31 | authorization?: string 32 | } 33 | request?: EndpointParams['request'] & { 34 | agent?: any 35 | fetch?: (url: string, init?: any) => Promise 36 | timeout?: number 37 | } 38 | } 39 | 40 | export interface Endpoint { 41 | (endpointRoute: string, endpointOptions?: EndpointParams): RequestOptions 42 | (endpointOptions: EndpointOptions): RequestOptions 43 | DEFAULTS: EndpointDefaults 44 | defaults(endpointOptions: EndpointParams): Endpoint 45 | merge( 46 | endpointRoute: string, 47 | endpointOptions?: EndpointParams 48 | ): EndpointDefaults 49 | merge(endpointOptions: EndpointParams): EndpointDefaults 50 | parse(endpointOptions: EndpointDefaults): RequestOptions 51 | } 52 | -------------------------------------------------------------------------------- /src/plugins/validate-request/validate.ts: -------------------------------------------------------------------------------- 1 | import { HTTPError } from '../../error' 2 | 3 | type APIClient = import('./types').APIClient 4 | type EndpointParams = import('./types').EndpointParams 5 | 6 | export function validate( 7 | _client: APIClient, 8 | endpointOptions: EndpointParams 9 | ): void { 10 | const { validate: params } = endpointOptions.request! 11 | 12 | if (!params) return 13 | 14 | for (const paramName of Object.keys(params)) { 15 | const param = params[paramName] 16 | const expectedType = param.type 17 | 18 | let value = endpointOptions[paramName] 19 | 20 | const valueIsPresent = typeof value !== 'undefined' 21 | 22 | if (!param.required && !valueIsPresent) { 23 | continue 24 | } 25 | 26 | if (param.required && !valueIsPresent) { 27 | throw new HTTPError(`parameter required: '${paramName}'`, 400) 28 | } 29 | 30 | if (expectedType === 'integer') { 31 | const unparsedValue = value 32 | value = parseInt(value, 10) 33 | if (isNaN(value)) { 34 | throw new HTTPError( 35 | `invalid value for parameter '${paramName}': ${JSON.stringify( 36 | unparsedValue 37 | )} is NaN`, 38 | 400 39 | ) 40 | } 41 | } 42 | 43 | if (expectedType === 'boolean') { 44 | const valueIsBoolean = typeof value === 'boolean' 45 | if (!valueIsBoolean) { 46 | throw new HTTPError( 47 | `invalid value for parameter '${paramName}': ${JSON.stringify( 48 | value 49 | )}`, 50 | 400 51 | ) 52 | } 53 | } 54 | 55 | if (param.enum && !param.enum.includes(value)) { 56 | throw new HTTPError( 57 | `invalid value for parameter '${paramName}': ${JSON.stringify(value)}`, 58 | 400 59 | ) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /scripts/generate-specification.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const fetch = require('node-fetch') 3 | const deepsort = require('deep-sort-object') 4 | 5 | const { writeToFile } = require('./utils/write-to-file') 6 | 7 | const specPath = path.resolve('specification') 8 | const srcPath = path.resolve('src') 9 | 10 | const writeSpecPartialJSON = async (filename, content) => { 11 | await writeToFile( 12 | path.resolve(specPath, `${filename}.json`), 13 | JSON.stringify(deepsort(content), null, 2) 14 | ) 15 | } 16 | 17 | const API_SPECIFICATION = 'https://api.bitbucket.org/swagger.json' 18 | 19 | fetch(API_SPECIFICATION) 20 | .then((response) => response.json()) 21 | .then(async ({ definitions, ...apiSpec }) => { 22 | await writeSpecPartialJSON('definitions', definitions) 23 | return apiSpec 24 | }) 25 | .then(async ({ paths, ...apiSpec }) => { 26 | await writeSpecPartialJSON('paths', paths) 27 | return apiSpec 28 | }) 29 | .then(async ({ securityDefinitions, ...apiSpec }) => { 30 | await writeSpecPartialJSON('securityDefinitions', securityDefinitions) 31 | 32 | await writeToFile( 33 | path.resolve(srcPath, `plugins/_oauth/spec.json`), 34 | JSON.stringify(deepsort(securityDefinitions.oauth2), null, 2) 35 | ) 36 | return apiSpec 37 | }) 38 | .then(async ({ tags, ...apiSpec }) => { 39 | await writeSpecPartialJSON('tags', tags) 40 | return apiSpec 41 | }) 42 | .then(async ({ 'x-radar-pages': xRadarPages, ...apiSpec }) => { 43 | writeSpecPartialJSON('others', apiSpec) 44 | 45 | delete apiSpec.info 46 | delete apiSpec['x-atlassian-narrative'] 47 | delete apiSpec['x-revision'] 48 | await writeToFile( 49 | path.resolve(srcPath, `endpoint/spec.json`), 50 | JSON.stringify(deepsort(apiSpec), null, 2) 51 | ) 52 | }) 53 | .catch((err) => { 54 | console.error(err) 55 | process.exit(1) 56 | }) 57 | -------------------------------------------------------------------------------- /templates/bitbucket.d.ts.mustache: -------------------------------------------------------------------------------- 1 | import { 2 | APIClient as BareAPIClient, 3 | Options as BareOptions, 4 | Plugin 5 | } from '../src/client/types' 6 | import { Routes } from '../src/plugins/register-endpoints/types' 7 | import { PaginatedResponseData, Response } from '../src/request/types' 8 | import { AuthOptions } from '../src/plugins/auth/types' 9 | 10 | interface Options extends BareOptions { 11 | auth?: AuthOptions 12 | notice?: boolean 13 | } 14 | 15 | type AsyncResponse = Promise> 16 | 17 | export interface APIClient extends BareAPIClient { 18 | hasNextPage(data: PaginatedResponseData): boolean 19 | getNextPage(data: PaginatedResponseData): Response 20 | hasPreviousPage(data: PaginatedResponseData): boolean 21 | getPreviousPage(data: PaginatedResponseData): Response 22 | 23 | registerEndpoints(routes: Routes): void 24 | } 25 | 26 | export interface APIClientFactory { 27 | new (options?: Options): APIClient 28 | (options?: Options): APIClient 29 | 30 | plugins(plugins: Plugin[]): APIClientFactory 31 | } 32 | 33 | export namespace Schema { 34 | export type Any = any 35 | export type AnyObject = { [key: string]: any } 36 | 37 | {{{typesBlob}}} 38 | } 39 | 40 | export namespace Params { 41 | export type Empty = {} 42 | 43 | {{#namespaces}} 44 | {{#endpoints}} 45 | {{^exclude}} 46 | {{#paramsType}} 47 | export type {{paramsType}} = { 48 | {{#params}} 49 | {{name}}{{^required}}?{{/required}}: {{#schema}}Schema.{{/schema}}{{{type}}} 50 | {{/params}} 51 | } 52 | {{/paramsType}} 53 | {{/exclude}} 54 | {{/endpoints}} 55 | {{/namespaces}} 56 | } 57 | 58 | export interface APIEndpoints { 59 | {{#namespaces}} 60 | {{namespace}}: { 61 | {{#endpoints}} 62 | {{name}}: (params: Params.{{paramsType}}) => AsyncResponse 63 | {{/endpoints}} 64 | } 65 | {{/namespaces}} 66 | } 67 | -------------------------------------------------------------------------------- /v1_API_NAME_CHANGES.md: -------------------------------------------------------------------------------- 1 | # Breaking Changes for API Name 2 | 3 | Unfortunately, API Name changes were published on the following minor version updates: 4 | 5 | - `v1.8.0` 6 | - `v1.9.0` 7 | - `v1.10.0` 8 | 9 | This type of changes won't happen again for minor version updates anymore. 10 | 11 | ## v1.8.0 12 | 13 | **deploy** 14 | 15 | ``` 16 | getAllKeys -> listKeys 17 | ``` 18 | 19 | **repositories** 20 | 21 | ``` 22 | getAllDeployKeys -> listDeployKeys 23 | ``` 24 | 25 | ## v1.9.0 26 | 27 | **teams** 28 | 29 | ``` 30 | listTeamPermissions -> listPermissions 31 | listRepositoryPermissions -> listPermissionsForRepos 32 | ``` 33 | 34 | **user** 35 | 36 | ``` 37 | listRepositoryPermissions -> listPermissionsForRepos 38 | listTeamPermissions -> listPermissionsForTeams 39 | ``` 40 | 41 | ## v1.10.0 42 | 43 | **commits** 44 | 45 | ``` 46 | fetchAll -> listAlt 47 | listFor -> listAt 48 | fetchAllFor ->listAtAlt 49 | ``` 50 | 51 | **pullrequests** 52 | 53 | ``` 54 | getForCommit -> listForCommit 55 | getActivityLog -> listActivities 56 | listActivityLog -> listActivitiesForRepo 57 | ``` 58 | 59 | **repositories** 60 | 61 | ``` 62 | getPullrequestsForCommit -> listPullrequestsForCommit 63 | fetchAllCommits -> listCommitsAlt 64 | listCommitsFor -> listCommitsAt 65 | fetchAllCommitsFor -> listCommitsAtAlt 66 | getPullRequestActivityLog -> listPullRequestActivities 67 | listPullRequestsActivityLog -> listPullRequestActivitiesForRepo 68 | getSrcMainRoot -> readSrcRoot 69 | getSrc -> readSrc 70 | ``` 71 | 72 | **snippets** 73 | 74 | ``` 75 | deleteCommit -> deleteAt 76 | getCommit -> getAt 77 | updateCommit -> updateAt 78 | listCommitsFor -> getCommit 79 | ``` 80 | 81 | **source** 82 | 83 | ``` 84 | getMainRoot -> readRoot 85 | get -> read 86 | ``` 87 | 88 | **teams** 89 | 90 | ``` 91 | listMembers -> getAllMembers 92 | ``` 93 | -------------------------------------------------------------------------------- /src/plugins/_oauth/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "authorizationUrl": "https://bitbucket.org/site/oauth2/authorize", 3 | "description": "OAuth 2 as per [RFC-6749](https://tools.ietf.org/html/rfc6749).", 4 | "flow": "accessCode", 5 | "scopes": { 6 | "account": "Read your account information", 7 | "account:write": "Read and modify your account information", 8 | "email": "Read your account's primary email address", 9 | "issue": "Read your repositories' issues", 10 | "issue:write": "Read and modify your repositories' issues", 11 | "pipeline": "Access your repositories' build pipelines", 12 | "pipeline:variable": "Access your repositories' build pipelines and configure their variables", 13 | "pipeline:write": "Access and rerun your repositories' build pipelines", 14 | "project": "Read your workspace's project settings and read repositories contained within your workspace's projects", 15 | "project:admin": "Read and modify settings for projects in your workspace", 16 | "pullrequest": "Read your repositories and their pull requests", 17 | "pullrequest:write": "Read and modify your repositories and their pull requests", 18 | "repository": "Read your repositories", 19 | "repository:admin": "Administer your repositories", 20 | "repository:delete": "Delete your repositories", 21 | "repository:write": "Read and modify your repositories", 22 | "runner": "Access your workspaces/repositories' runners", 23 | "runner:write": "Access and edit your workspaces/repositories' runners", 24 | "snippet": "Read your snippets", 25 | "snippet:write": "Read and modify your snippets", 26 | "team": "Read your team membership information", 27 | "team:write": "Read and modify your team membership information", 28 | "webhook": "Read and modify your repositories' webhooks", 29 | "wiki": "Read and modify your repositories' wikis" 30 | }, 31 | "tokenUrl": "https://bitbucket.org/site/oauth2/access_token", 32 | "type": "oauth2" 33 | } 34 | -------------------------------------------------------------------------------- /src/plugins/register-endpoints/register-endpoints.ts: -------------------------------------------------------------------------------- 1 | type APIClient = import('./types').APIClient 2 | type Routes = import('./types').Routes 3 | type EndpointDefaults = import('./types').EndpointDefaults 4 | 5 | export function registerEndpoints(client: APIClient, routes: Routes): void { 6 | for (const namespaceName of Object.keys(routes)) { 7 | if (!client[namespaceName]) { 8 | client[namespaceName] = {} 9 | } 10 | 11 | for (const endpointName of Object.keys(routes[namespaceName])) { 12 | let endpointObject = routes[namespaceName][endpointName] 13 | 14 | if (endpointObject.alias) { 15 | const [namespaceAlias, apiAlias] = endpointObject.alias.split('.') 16 | endpointObject = routes[namespaceAlias][apiAlias] 17 | } 18 | 19 | const endpointDefaults = ['accepts', 'method', 'url', 'headers'].reduce( 20 | (defaults: any, key: string): any => { 21 | if (key in endpointObject) { 22 | defaults[key] = endpointObject[key as keyof typeof endpointObject] 23 | } 24 | return defaults 25 | }, 26 | {} 27 | ) as EndpointDefaults 28 | 29 | endpointDefaults.request = { 30 | validate: endpointObject.params, 31 | } 32 | 33 | const request = client.request.defaults(endpointDefaults) 34 | 35 | if (endpointObject.deprecated) { 36 | client[namespaceName][endpointName] = function deprecatedEndpoint( 37 | ...args: Parameters 38 | ): ReturnType { 39 | // eslint-disable-next-line no-console 40 | console.log( 41 | `\x1b[43m\x1b[30m %s \x1b[0m\x1b[33m %s \x1b[0m`, 42 | `DEPRECATION WARNING:`, 43 | `${endpointDefaults.method} ${endpointDefaults.url as string}` 44 | ) 45 | 46 | client[namespaceName][endpointName] = request 47 | 48 | return request(...args) 49 | } 50 | 51 | continue 52 | } 53 | 54 | client[namespaceName][endpointName] = request 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /scripts/generate-routes/process-parameters.js: -------------------------------------------------------------------------------- 1 | const deepmerge = require('deepmerge') 2 | const { uniq } = require('lodash') 3 | const { pascalCase } = require('../utils/pascal-case') 4 | 5 | const usernameRegex = /\/\{username\}/ 6 | const workspaceRegex = /\/\{workspace\}/ 7 | 8 | const fixUrlParameters = (endpointObject, url) => { 9 | const needWorkspaceUrlParam = workspaceRegex.test(url) 10 | const needUsernameUrlParam = usernameRegex.test(url) 11 | 12 | if (needWorkspaceUrlParam && !endpointObject.params.workspace) { 13 | endpointObject.params.workspace = { 14 | in: 'path', 15 | required: true, 16 | type: 'string', 17 | } 18 | } 19 | 20 | if (!needWorkspaceUrlParam) { 21 | delete endpointObject.params.workspace 22 | } 23 | 24 | if (needUsernameUrlParam && !endpointObject.params.username) { 25 | endpointObject.params.username = { 26 | in: 'path', 27 | required: true, 28 | type: 'string', 29 | } 30 | } 31 | 32 | if (!needUsernameUrlParam) { 33 | delete endpointObject.params.username 34 | } 35 | } 36 | 37 | const processParameters = (endpointObject, { parameters = [] } = {}, url) => { 38 | for (const { 39 | enum: _enum, 40 | in: _in, 41 | name, 42 | required, 43 | schema = {}, 44 | type = 'any', 45 | deleted, 46 | } of parameters) { 47 | if (deleted) { 48 | delete endpointObject.params[name] 49 | continue 50 | } 51 | 52 | if (!endpointObject.params[name]) { 53 | endpointObject.params[name] = {} 54 | } 55 | 56 | endpointObject.params[name] = deepmerge(endpointObject.params[name], { 57 | enum: _enum, 58 | in: _in, 59 | required, 60 | type, 61 | }) 62 | 63 | endpointObject.params[name].enum = uniq(endpointObject.params[name].enum) 64 | 65 | if (!required) { 66 | delete endpointObject.params[name].required 67 | } 68 | 69 | if (schema.$ref) { 70 | endpointObject.params[name].schema = pascalCase( 71 | schema.$ref.replace('#/definitions/', '') 72 | ) 73 | } 74 | } 75 | 76 | fixUrlParameters(endpointObject, url) 77 | } 78 | 79 | module.exports.processParameters = processParameters 80 | -------------------------------------------------------------------------------- /src/request/fetch-wrapper.ts: -------------------------------------------------------------------------------- 1 | import nodeFetch from 'node-fetch' 2 | import { HTTPError } from '../error' 3 | 4 | type FetchResponse = import('node-fetch').Response 5 | type Endpoint = import('./types').Endpoint 6 | type Headers = import('./types').Headers 7 | type Response = import('./types').Response 8 | 9 | function getData(response: FetchResponse): Promise { 10 | const contentType = response.headers.get('content-type') 11 | 12 | if (contentType!.includes('application/json')) { 13 | return response.json() 14 | } 15 | 16 | if (!contentType || /^text\/|charset=utf-8$/.test(contentType)) { 17 | return response.text() 18 | } 19 | 20 | return response.arrayBuffer() 21 | } 22 | 23 | export function fetchWrapper( 24 | requestOptions: ReturnType 25 | ): Promise> { 26 | const { method, url, headers, body, request } = requestOptions 27 | 28 | const options = Object.assign({ method, body, headers }, request) 29 | 30 | let responseStatus: number 31 | let responseUrl: string 32 | 33 | const responseHeaders: Headers = {} 34 | 35 | const fetch = request!.fetch ?? nodeFetch 36 | 37 | return fetch(url, options) 38 | .then((response: FetchResponse): any => { 39 | responseStatus = response.status 40 | responseUrl = response.url 41 | 42 | for (const [field, value] of response.headers) { 43 | responseHeaders[field] = value 44 | } 45 | 46 | if (response.status >= 400 || [304].includes(response.status)) { 47 | return getData(response).then((error): void => { 48 | throw new HTTPError(response.statusText, responseStatus, { 49 | error, 50 | headers: responseHeaders, 51 | request: requestOptions, 52 | }) 53 | }) 54 | } 55 | 56 | return getData(response) 57 | }) 58 | .then( 59 | (data): Response => ({ 60 | data, 61 | headers: responseHeaders, 62 | status: responseStatus, 63 | url: responseUrl, 64 | }) 65 | ) 66 | .catch((error): any => { 67 | if (error instanceof HTTPError) { 68 | throw error 69 | } 70 | 71 | throw new HTTPError(error.message, 500, { 72 | headers: responseHeaders, 73 | request: requestOptions, 74 | }) 75 | }) 76 | } 77 | -------------------------------------------------------------------------------- /scripts/generate-types-schema.js: -------------------------------------------------------------------------------- 1 | const deepsort = require('deep-sort-object') 2 | const deepmerge = require('deepmerge') 3 | const { chain } = require('lodash') 4 | const { resolve: resolvePath } = require('path') 5 | 6 | const { pascalCase } = require('./utils/pascal-case') 7 | const { writeToFile } = require('./utils/write-to-file') 8 | 9 | const DEFINITIONS_SPEC = require('../specification/definitions.json') 10 | const PATHS_SPEC = require('../specification/paths.json') 11 | 12 | const noiseKeys = [ 13 | 'description', 14 | 'format', 15 | 'minimum', 16 | 'minItems', 17 | 'pattern', 18 | 'readOnly', 19 | 'title', 20 | 'uniqueItems', 21 | ] 22 | 23 | function deleteNoises(object) { 24 | noiseKeys.forEach((noiseKey) => { 25 | delete object[noiseKey] 26 | }) 27 | } 28 | 29 | function deleteNoisesDeep(object) { 30 | deleteNoises(object) 31 | 32 | if (object.items) { 33 | deleteNoisesDeep(object.items) 34 | } 35 | 36 | if (object.properties) { 37 | Object.values(object.properties).forEach(deleteNoisesDeep) 38 | } 39 | } 40 | 41 | const entityNames = chain(DEFINITIONS_SPEC).keys().uniq().sort().value() 42 | 43 | const schema = { 44 | type: 'object', 45 | definitions: {}, 46 | properties: {}, 47 | } 48 | 49 | for (const [entityName, entity] of Object.entries(DEFINITIONS_SPEC)) { 50 | deleteNoisesDeep(entity) 51 | entity.allOf && entity.allOf.forEach(deleteNoisesDeep) 52 | schema.definitions[entityName] = deepmerge({}, entity) 53 | } 54 | 55 | const stringifiedPathsSpec = JSON.stringify(PATHS_SPEC) 56 | 57 | for (const entityName of entityNames) { 58 | if (RegExp(`"#/definitions/${entityName}"`).test(stringifiedPathsSpec)) { 59 | schema.properties[entityName] = { 60 | $ref: `#/definitions/${entityName}`, 61 | } 62 | } 63 | } 64 | 65 | let stringifiedSchema = JSON.stringify(deepsort(schema), null, 2) 66 | 67 | for (const entityName of entityNames) { 68 | stringifiedSchema = chain(stringifiedSchema) 69 | .replace( 70 | RegExp(`^ {0,4}"${entityName}":`, 'gm'), 71 | ` "${pascalCase(entityName)}":` 72 | ) 73 | .replace( 74 | RegExp(`"#/definitions/${entityName}"`, 'g'), 75 | `"#/definitions/${pascalCase(entityName)}"` 76 | ) 77 | .value() 78 | } 79 | 80 | const typesSchemaPath = resolvePath('templates/types-schema.json') 81 | 82 | writeToFile(typesSchemaPath, stringifiedSchema).catch((err) => { 83 | console.error(err) 84 | process.exit(1) 85 | }) 86 | -------------------------------------------------------------------------------- /src/endpoint/parse.ts: -------------------------------------------------------------------------------- 1 | import urlTemplate from 'url-template' 2 | import { addQueryParameters } from './utils/add-query-parameters' 3 | import { extractUrlVariableNames } from './utils/extract-url-variable-names' 4 | 5 | type EndpointDefaults = import('./types').EndpointDefaults 6 | type EndpointParams = import('./types').EndpointParams 7 | type RequestMethod = import('./types').RequestMethod 8 | type RequestOptions = import('./types').RequestOptions 9 | 10 | const contentType = { 11 | formData: 'multipart/form-data', 12 | urlEncoded: 'application/x-www-form-urlencoded', 13 | json: 'application/json; charset=utf-8', 14 | } 15 | 16 | export function parse(endpointOptions: EndpointDefaults): RequestOptions { 17 | const { 18 | accepts = [], 19 | method: _method, 20 | baseUrl, 21 | url: _url, 22 | headers, 23 | request, 24 | ...params 25 | } = endpointOptions 26 | 27 | const method = _method.toUpperCase() as RequestMethod 28 | 29 | const urlVariableNames = extractUrlVariableNames(_url) 30 | 31 | let url = urlTemplate.parse(_url).expand(params) 32 | if (!/^http/.test(url)) { 33 | url = `${baseUrl}${url}` 34 | } 35 | 36 | const { _body, ...remainingParams } = Object.keys(params).reduce( 37 | (bodyParams: EndpointParams, key): EndpointParams => { 38 | if (!urlVariableNames.includes(key)) bodyParams[key] = params[key] 39 | return bodyParams 40 | }, 41 | {} 42 | ) 43 | 44 | let body: any 45 | let bodyIsFormData = false 46 | 47 | if (['GET', 'DELETE'].includes(method)) { 48 | url = addQueryParameters(url, remainingParams) 49 | } else if (typeof _body !== 'undefined') { 50 | body = _body 51 | 52 | bodyIsFormData = /form-?data/i.test(body.constructor.name) 53 | 54 | if (bodyIsFormData && accepts.includes(contentType.formData)) { 55 | for (const paramName of Object.keys(remainingParams)) { 56 | body.append(paramName, remainingParams[paramName]) 57 | } 58 | } 59 | } else if (Object.keys(remainingParams).length) { 60 | body = remainingParams 61 | } 62 | 63 | if (!bodyIsFormData) { 64 | if (accepts.includes(contentType.urlEncoded)) { 65 | body = addQueryParameters('', body).substring(1) 66 | headers['content-type'] = contentType.urlEncoded 67 | } else { 68 | body = JSON.stringify(body) 69 | headers['content-type'] = contentType.json 70 | } 71 | } 72 | 73 | if (typeof body === 'undefined') { 74 | delete headers['content-type'] 75 | } 76 | 77 | return { 78 | method, 79 | url, 80 | body, 81 | headers, 82 | request, 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /specification/securityDefinitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": { 3 | "description": "API Keys can be used as Basic HTTP Authentication credentials and provide a substitute for the account's actual username and password. API Keys are only available to team accounts and there is only 1 key per account. API Keys do not support scopes and have therefore access to all contents of the account.", 4 | "in": "header", 5 | "name": "Authorization", 6 | "type": "apiKey" 7 | }, 8 | "basic": { 9 | "description": "Basic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported). Note that Basic Auth is available only with username and app password as credentials.", 10 | "type": "basic" 11 | }, 12 | "oauth2": { 13 | "authorizationUrl": "https://bitbucket.org/site/oauth2/authorize", 14 | "description": "OAuth 2 as per [RFC-6749](https://tools.ietf.org/html/rfc6749).", 15 | "flow": "accessCode", 16 | "scopes": { 17 | "account": "Read your account information", 18 | "account:write": "Read and modify your account information", 19 | "email": "Read your account's primary email address", 20 | "issue": "Read your repositories' issues", 21 | "issue:write": "Read and modify your repositories' issues", 22 | "pipeline": "Access your repositories' build pipelines", 23 | "pipeline:variable": "Access your repositories' build pipelines and configure their variables", 24 | "pipeline:write": "Access and rerun your repositories' build pipelines", 25 | "project": "Read your workspace's project settings and read repositories contained within your workspace's projects", 26 | "project:admin": "Read and modify settings for projects in your workspace", 27 | "pullrequest": "Read your repositories and their pull requests", 28 | "pullrequest:write": "Read and modify your repositories and their pull requests", 29 | "repository": "Read your repositories", 30 | "repository:admin": "Administer your repositories", 31 | "repository:delete": "Delete your repositories", 32 | "repository:write": "Read and modify your repositories", 33 | "runner": "Access your workspaces/repositories' runners", 34 | "runner:write": "Access and edit your workspaces/repositories' runners", 35 | "snippet": "Read your snippets", 36 | "snippet:write": "Read and modify your snippets", 37 | "team": "Read your team membership information", 38 | "team:write": "Read and modify your team membership information", 39 | "webhook": "Read and modify your repositories' webhooks", 40 | "wiki": "Read and modify your repositories' wikis" 41 | }, 42 | "tokenUrl": "https://bitbucket.org/site/oauth2/access_token", 43 | "type": "oauth2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /scripts/generate-endpoint-names.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const deepsort = require('deep-sort-object') 3 | const get = require('lodash/get') 4 | 5 | const { 6 | extractNamespaceFromURL, 7 | } = require('./utils/extract-namespace-from-url') 8 | const { writeToFile } = require('./utils/write-to-file') 9 | 10 | const PATHS_SPEC = require('../specification/paths.json') 11 | const NON_METHOD_KEYS = ['parameters'] 12 | 13 | const endpointNamesPath = path.resolve( 14 | 'specification/extras', 15 | 'endpoint-names.json' 16 | ) 17 | 18 | const ENDPOINT_NAMES = require(endpointNamesPath) 19 | 20 | const workspaceRegex = /\/\{workspace\}/ 21 | 22 | // trying to dodge the ever moving breaking changes 23 | const namespaceNameMap = { 24 | branch_restrictions: 'branchrestrictions', 25 | commit_statuses: 'commitstatuses', 26 | } 27 | 28 | const formatNamespaceName = (namespaceName) => { 29 | const name = namespaceName.toLowerCase().replace(/ /g, '_') 30 | return namespaceNameMap[name] || name 31 | } 32 | 33 | for (const url of Object.keys(PATHS_SPEC)) { 34 | const resourceNamespaceName = formatNamespaceName( 35 | extractNamespaceFromURL(url) 36 | ) 37 | 38 | if (!ENDPOINT_NAMES[url]) { 39 | // new endpoint url 40 | ENDPOINT_NAMES[url] = {} 41 | } 42 | 43 | if (workspaceRegex.test(url)) { 44 | const legacyUsernameUrl = url.replace(workspaceRegex, '/{username}') 45 | 46 | const legacyUsernameEndpointNames = get(ENDPOINT_NAMES, legacyUsernameUrl) 47 | 48 | if (legacyUsernameEndpointNames) { 49 | ENDPOINT_NAMES[url] = legacyUsernameEndpointNames 50 | delete ENDPOINT_NAMES[legacyUsernameUrl] 51 | } 52 | } 53 | 54 | for (const method of Object.keys(PATHS_SPEC[url])) { 55 | if (NON_METHOD_KEYS.includes(method)) continue 56 | 57 | if (!ENDPOINT_NAMES[url][method]) { 58 | // new endpoint 59 | ENDPOINT_NAMES[url][method] = {} 60 | } 61 | 62 | const endpointName = PATHS_SPEC[url][method].operationId || '' 63 | 64 | if (!ENDPOINT_NAMES[url][method][resourceNamespaceName]) { 65 | ENDPOINT_NAMES[url][method][resourceNamespaceName] = endpointName 66 | } 67 | 68 | if (!PATHS_SPEC[url][method].tags) continue 69 | 70 | for (const tagNamespaceName of PATHS_SPEC[url][method].tags.map( 71 | formatNamespaceName 72 | )) { 73 | if (!ENDPOINT_NAMES[url][method][tagNamespaceName]) { 74 | ENDPOINT_NAMES[url][method][tagNamespaceName] = endpointName 75 | } 76 | } 77 | } 78 | } 79 | 80 | const existingEndpointUrls = Object.keys(PATHS_SPEC) 81 | const nonExistentEndpointUrls = Object.keys(ENDPOINT_NAMES).filter( 82 | (url) => !existingEndpointUrls.includes(url) 83 | ) 84 | 85 | if (nonExistentEndpointUrls.length !== 0) { 86 | throw new Error( 87 | [ 88 | `Non-existent URLs:`, 89 | ...nonExistentEndpointUrls.map((url) => `- ${url}`), 90 | ].join('\n') 91 | ) 92 | } 93 | 94 | writeToFile( 95 | endpointNamesPath, 96 | JSON.stringify(deepsort(ENDPOINT_NAMES), null, 2) 97 | ).catch((err) => { 98 | console.error(err) 99 | process.exit(1) 100 | }) 101 | -------------------------------------------------------------------------------- /src/plugins/_oauth/routes.js: -------------------------------------------------------------------------------- 1 | const getOAuthRoutes = (info) => ({ 2 | getToken: { 3 | authorizationCodeGrant: { 4 | grant_type: 'authorization_code', 5 | method: 'POST', 6 | params: { 7 | client_id: { 8 | in: 'body', 9 | required: true, 10 | type: 'string', 11 | }, 12 | client_secret: { 13 | in: 'body', 14 | required: true, 15 | type: 'string', 16 | }, 17 | code: { 18 | required: true, 19 | type: 'string', 20 | }, 21 | grant_type: { 22 | in: 'body', 23 | required: true, 24 | type: 'string', 25 | }, 26 | }, 27 | url: `${info.tokenUrl}`, 28 | }, 29 | implicitGrant: { 30 | response_type: 'token', 31 | method: 'GET', 32 | params: { 33 | client_id: { 34 | in: 'query', 35 | required: true, 36 | type: 'string', 37 | }, 38 | response_type: { 39 | in: 'query', 40 | required: true, 41 | type: 'string', 42 | }, 43 | }, 44 | url: `${info.tokenUrl}`, 45 | }, 46 | resourceOwnerPasswordCredentialsGrant: { 47 | grant_type: 'password', 48 | method: 'POST', 49 | params: { 50 | client_id: { 51 | in: 'body', 52 | required: true, 53 | type: 'string', 54 | }, 55 | client_secret: { 56 | in: 'body', 57 | required: true, 58 | type: 'string', 59 | }, 60 | grant_type: { 61 | in: 'body', 62 | required: true, 63 | type: 'string', 64 | }, 65 | password: { 66 | in: 'body', 67 | required: true, 68 | type: 'string', 69 | }, 70 | username: { 71 | in: 'body', 72 | required: true, 73 | type: 'string', 74 | }, 75 | }, 76 | url: `${info.tokenUrl}`, 77 | }, 78 | clientCredentialsGrant: { 79 | grant_type: 'client_credentials', 80 | method: 'POST', 81 | params: { 82 | client_id: { 83 | in: 'body', 84 | required: true, 85 | type: 'string', 86 | }, 87 | client_secret: { 88 | in: 'body', 89 | required: true, 90 | type: 'string', 91 | }, 92 | grant_type: { 93 | in: 'body', 94 | required: true, 95 | type: 'string', 96 | }, 97 | }, 98 | url: `${info.tokenUrl}`, 99 | }, 100 | bitbucketCloudJWTGrant: { 101 | grant_type: 'urn:bitbucket:oauth2:jwt', 102 | method: 'POST', 103 | params: { 104 | grant_type: { 105 | in: 'body', 106 | required: true, 107 | type: 'string', 108 | }, 109 | jwtToken: { 110 | in: 'headers.authorization:JWT', 111 | required: true, 112 | type: 'string', 113 | }, 114 | }, 115 | url: `${info.tokenUrl}`, 116 | }, 117 | }, 118 | }) 119 | 120 | module.exports = getOAuthRoutes 121 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitbucket", 3 | "version": "2.12.0", 4 | "description": "Bitbucket API client for Browser and Node.js", 5 | "keywords": [ 6 | "bitbucket", 7 | "bitbucket-api", 8 | "api-client", 9 | "api", 10 | "rest" 11 | ], 12 | "homepage": "https://github.com/MunifTanjim/node-bitbucket#readme", 13 | "bugs": "https://github.com/MunifTanjim/node-bitbucket/issues", 14 | "license": "MIT", 15 | "author": "MunifTanjim (https://muniftanjim.dev)", 16 | "files": [ 17 | "lib", 18 | "src/**/types.ts" 19 | ], 20 | "main": "lib/index.umd.js", 21 | "module": "lib/index.js", 22 | "unpkg": "lib/index.umd.js", 23 | "jsdelivr": "lib/index.umd.js", 24 | "types": "lib/index.d.ts", 25 | "repository": "github:MunifTanjim/node-bitbucket", 26 | "scripts": { 27 | "build": "npm-run-all -s build:core build:plugins", 28 | "prebuild:core": "rimraf lib/*.js lib/*.js.map", 29 | "build:core": "bili", 30 | "prebuild:plugins": "rimraf lib/plugins/*.js lib/plugins/*.js.map", 31 | "build:plugins": "npm-run-all -s build:plugin:*", 32 | "build:plugin:authenticate": "PLUGIN=authenticate bili", 33 | "predocs:build": "mkdirp docs && rimraf docs/*", 34 | "docs:build": "npm run generate:api-docs", 35 | "postdocs:build": "apidoc -i docs -o docs", 36 | "clean": "rimraf lib/*", 37 | "generate:specification": "node scripts/generate-specification", 38 | "generate:endpoint-names": "node scripts/generate-endpoint-names", 39 | "generate:routes": "node scripts/generate-routes", 40 | "generate:api-docs": "node scripts/generate-api-docs", 41 | "generate:types-schema": "node scripts/generate-types-schema", 42 | "pregenerate:types": "rimraf lib/*.d.ts lib/plugins/*.d.ts", 43 | "generate:types": "node scripts/generate-types", 44 | "postgenerate:types": "npm run validate:types", 45 | "prepack": "npm-run-all -p build generate:types", 46 | "update": "./scripts/update.sh", 47 | "validate": "npm-run-all -p validate:*", 48 | "validate:types": "npm-run-all -p validate:types:*", 49 | "validate:types:bitbucket": "tsc --noEmit lib/bitbucket.d.ts", 50 | "validate:types:index": "tsc --noEmit lib/index.d.ts", 51 | "validate:types:minimal": "tsc --noEmit lib/minimal.d.ts", 52 | "validate:types:plugin:authenticate": "tsc --noEmit lib/plugins/authenticate.d.ts", 53 | "test": "jest" 54 | }, 55 | "dependencies": { 56 | "before-after-hook": "^2.1.0", 57 | "deepmerge": "^4.2.2", 58 | "is-plain-object": "^3.0.0", 59 | "node-fetch": "^2.6.0", 60 | "url-template": "^2.0.8" 61 | }, 62 | "devDependencies": { 63 | "@types/jest": "^25.2.3", 64 | "@types/mustache": "^4.0.1", 65 | "@types/node": "^14.0.5", 66 | "@types/node-fetch": "^2.5.7", 67 | "@types/url-template": "^2.0.28", 68 | "@typescript-eslint/eslint-plugin": "^2.23.0", 69 | "@typescript-eslint/parser": "^2.23.0", 70 | "apidoc": "0.17.5", 71 | "bili": "^4.10.0", 72 | "clean-deep": "^3.3.0", 73 | "deep-sort-object": "^1.0.2", 74 | "eslint": "^6.8.0", 75 | "eslint-config-prettier": "^6.10.0", 76 | "eslint-config-standard-with-typescript": "^15.0.1", 77 | "eslint-plugin-import": "^2.20.1", 78 | "eslint-plugin-jest": "^23.8.2", 79 | "eslint-plugin-node": "^11.0.0", 80 | "eslint-plugin-prettier": "^3.1.2", 81 | "eslint-plugin-promise": "^4.2.1", 82 | "eslint-plugin-standard": "^4.0.1", 83 | "husky": "^4.2.5", 84 | "jest": "^26.0.1", 85 | "json-schema-to-typescript": "^9.1.0", 86 | "lint-staged": "^10.2.6", 87 | "lodash-es": "^4.17.11", 88 | "mkdirp": "^1.0.4", 89 | "mustache": "^4.0.1", 90 | "npm-run-all": "^4.1.5", 91 | "prettier": "^2.0.5", 92 | "pretty-quick": "^2.0.1", 93 | "rimraf": "^3.0.2", 94 | "rollup-plugin-typescript2": "^0.27.1", 95 | "ts-jest": "^26.0.0", 96 | "ts-node": "^8.10.1", 97 | "typescript": "^3.8.3" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![version:@latest](https://img.shields.io/npm/v/bitbucket.svg?style=for-the-badge)](https://www.npmjs.com/package/bitbucket) 2 | [![Documentation](https://img.shields.io/badge/docs-bitbucket.js-blue.svg?style=for-the-badge)](https://bitbucketjs.netlify.app) 3 | [![License](https://img.shields.io/github/license/MunifTanjim/node-bitbucket.svg?style=for-the-badge)](https://github.com/MunifTanjim/node-bitbucket/blob/master/LICENSE) 4 | 5 | # Bitbucket.js 6 | 7 | Bitbucket API client for Browser and Node.js 8 | 9 | Bitbucket API docs: [https://api.bitbucket.org](https://api.bitbucket.org) 10 | 11 | --- 12 | 13 | **BITBUCKET CLOUD API LATEST UPDATES**: [https://developer.atlassian.com/cloud/bitbucket](https://developer.atlassian.com/cloud/bitbucket) 14 | 15 | --- 16 | 17 | ## Installation 18 | 19 | via **npm**: 20 | 21 | ```sh 22 | $ npm install --save bitbucket 23 | ``` 24 | 25 | via **yarn**: 26 | 27 | ```sh 28 | $ yarn add bitbucket 29 | ``` 30 | 31 | ## Usage 32 | 33 | ### Browser 34 | 35 | ```html 36 | 37 | 40 | ``` 41 | 42 | ### Node 43 | 44 | ```js 45 | const { Bitbucket } = require('bitbucket') 46 | 47 | const bitbucket = new Bitbucket() 48 | ``` 49 | 50 | #### Client Options 51 | 52 | You can set the APIs' `baseUrl` and modify some behaviors (e.g. request timeout etc.) by passing a clientOptions object to the `Bitbucket` constructor. 53 | 54 | ```js 55 | const clientOptions = { 56 | baseUrl: 'https://api.bitbucket.org/2.0', 57 | request: { 58 | timeout: 10, 59 | }, 60 | } 61 | 62 | const bitbucket = new Bitbucket(clientOptions) 63 | ``` 64 | 65 | #### Authentication 66 | 67 | **Using `username` and `password`**: 68 | 69 | ```js 70 | const clientOptions = { 71 | auth: { 72 | username: 'username', 73 | password: 'password', 74 | }, 75 | } 76 | 77 | const bitbucket = new Bitbucket(clientOptions) 78 | ``` 79 | 80 | **Using `token`**: 81 | 82 | ```js 83 | const clientOptions = { 84 | auth: { 85 | token: 'abcdef123456', 86 | }, 87 | } 88 | 89 | const bitbucket = new Bitbucket(clientOptions) 90 | ``` 91 | 92 | #### API Methods 93 | 94 | **async/await** 95 | 96 | ```js 97 | try { 98 | const { data, headers, status, url } = await bitbucket..({ ...params }) 99 | } catch (err) { 100 | const { message, error, headers, request, status } = err 101 | } 102 | ``` 103 | 104 | **Promise** 105 | 106 | ```js 107 | bitbucket. 108 | .({ ...params }) 109 | .then(({ data, headers, status, url }) => {}) 110 | .catch(({ message, error, headers, request, status }) => {}) 111 | ``` 112 | 113 | Notes: 114 | 115 | - `` is one of the _Namespace Names_ 116 | - `` is one of the _API Names_ 117 | 118 | #### Namespace Names 119 | 120 | `branching_model`, `branchrestrictions`, `commits`, `commitstatuses`, `deploy`, `deployments`, `downloads`, `hook_events`, `issue_tracker`, `pipelines`, `projects`, `pullrequests`, `refs`, `repositories`, `search`, `snippet`, `snippets`, `source`, `ssh`, `teams`, `user`, `users`, `webhooks` 121 | 122 | #### API Names 123 | 124 | Check API client docs: [https://bitbucketjs.netlify.com](https://bitbucketjs.netlify.com) 125 | 126 | ##### Examples 127 | 128 | ```js 129 | bitbucket.repositories 130 | .listGlobal({}) 131 | .then(({ data }) => console.log(data.values)) 132 | .catch((err) => console.error(err)) 133 | ``` 134 | 135 | ## Acknowledgement 136 | 137 | This API client is heavily inspired by the **[`octokit/rest.js`](https://github.com/octokit/rest.js/)** and a lot of ideas are taken from there. So, thanks goes to the maintainer [Gregor Martynus](https://github.com/gr2m) and all the [awesome contributors](https://github.com/octokit/rest.js/graphs/contributors) of `octokit/rest.js`. 138 | 139 | ## License 140 | 141 | Licensed under the MIT License. Check the [LICENSE](https://github.com/MunifTanjim/node-bitbucket/blob/master/LICENSE) file for details. 142 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [Unreleased] 4 | 5 | - ... 6 | 7 | ## [2.12.0] - 2024-05-18 8 | 9 | - Generate from latest API Specification 10 | 11 | ## [2.11.0] - 2023-03-08 12 | 13 | - Generate from latest API Specification 14 | 15 | ## [2.10.0] - 2023-02-18 16 | 17 | - Generate from latest API Specification 18 | 19 | ## [2.9.0] - 2022-10-28 20 | 21 | - Generate from latest API Specification 22 | 23 | ## [2.8.0] - 2022-09-09 24 | 25 | - Generate from latest API Specification 26 | 27 | ## [2.7.0] - 2021-11-18 28 | 29 | - Generate from latest API Specification 30 | 31 | ## [2.6.3] - 2021-09-04 32 | 33 | - Generate from latest API Specification 34 | 35 | ## [2.6.2] - 2021-07-02 36 | 37 | - Generate from latest API Specification 38 | 39 | ## [2.6.1] - 2021-04-24 40 | 41 | - Remove dependency `@node-fetch/btoa-lite` 42 | 43 | ## [2.6.0] - 2021-04-23 44 | 45 | - Generate from latest API Specification 46 | 47 | ## [2.5.1] - 2021-02-26 48 | 49 | - Generate routes for missing item [80](https://github.com/MunifTanjim/node-bitbucket/issues/80) 50 | 51 | ## [2.5.0] - 2021-02-25 52 | 53 | - Generate from latest API Specification [#82](https://github.com/MunifTanjim/node-bitbucket/pull/82) 54 | 55 | ## [2.4.2] - 2020-10-14 56 | 57 | - Fix `endpoint/utils/add-query-parameters` 58 | 59 | ## [2.4.1] - 2020-07-21 60 | 61 | - Fix `endpoint/utils/add-query-parameters` 62 | 63 | ## [2.4.0] - 2020-07-15 64 | 65 | - Add endpoints for: `Workspace` 66 | - Update notice plugin 67 | 68 | ## [2.3.0] - 2020-06-29 69 | 70 | - Generate from latest API Specification 71 | 72 | ## [2.2.0] - 2020-05-23 73 | 74 | - Add better support for TypeScript types, resolves [#61](https://github.com/MunifTanjim/node-bitbucket/issues/61) 75 | 76 | ## [2.1.0] - 2020-05-02 77 | 78 | - Add endpoints for: `PropertyValue`, `Reports`, `Annotation` 79 | 80 | ## [2.0.3] - 2020-02-14 81 | 82 | - Resolve: [#51](https://github.com/MunifTanjim/node-bitbucket/issues/51) 83 | 84 | ## [2.0.2] - 2020-02-05 85 | 86 | - Export types: `APIEndpoints`, `Params`, `Schema` [#50](https://github.com/MunifTanjim/node-bitbucket/issues/50) 87 | 88 | ## [2.0.1] - 2020-02-04 89 | 90 | - Fix types [#50](https://github.com/MunifTanjim/node-bitbucket/issues/50) 91 | 92 | ## 2.0.0 - 2020-02-01 93 | 94 | - Version 2 Release 95 | 96 | [unreleased]: https://github.com/MunifTanjim/node-bitbucket/compare/2.12.0...HEAD 97 | [2.12.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.11.0...2.12.0 98 | [2.11.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.10.0...2.11.0 99 | [2.10.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.9.0...2.10.0 100 | [2.9.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.8.0...2.9.0 101 | [2.8.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.7.0...2.8.0 102 | [2.7.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.6.3...2.7.0 103 | [2.6.3]: https://github.com/MunifTanjim/node-bitbucket/compare/2.6.2...2.6.3 104 | [2.6.2]: https://github.com/MunifTanjim/node-bitbucket/compare/2.6.1...2.6.2 105 | [2.6.1]: https://github.com/MunifTanjim/node-bitbucket/compare/2.6.0...2.6.1 106 | [2.6.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.5.1...2.6.0 107 | [2.5.1]: https://github.com/MunifTanjim/node-bitbucket/compare/2.5.0...2.5.1 108 | [2.5.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.4.2...2.5.0 109 | [2.4.2]: https://github.com/MunifTanjim/node-bitbucket/compare/2.4.1...2.4.2 110 | [2.4.1]: https://github.com/MunifTanjim/node-bitbucket/compare/2.4.0...2.4.1 111 | [2.4.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.3.0...2.4.0 112 | [2.3.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.2.0...2.3.0 113 | [2.2.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.1.0...2.2.0 114 | [2.1.0]: https://github.com/MunifTanjim/node-bitbucket/compare/2.0.3...2.1.0 115 | [2.0.3]: https://github.com/MunifTanjim/node-bitbucket/compare/2.0.2...2.0.3 116 | [2.0.2]: https://github.com/MunifTanjim/node-bitbucket/compare/2.0.1...2.0.2 117 | [2.0.1]: https://github.com/MunifTanjim/node-bitbucket/compare/2.0.0...2.0.1 118 | -------------------------------------------------------------------------------- /specification/extras/paths.json: -------------------------------------------------------------------------------- 1 | { 2 | "/repositories/{workspace}/{repo_slug}/branching-model/settings": { 3 | "put": { 4 | "parameters": [ 5 | { 6 | "in": "body", 7 | "name": "_body", 8 | "required": false, 9 | "schema": { 10 | "$ref": "#/definitions/branching_model_settings" 11 | } 12 | } 13 | ] 14 | } 15 | }, 16 | "/repositories/{workspace}/{repo_slug}/commits": { 17 | "get": { 18 | "parameters": [ 19 | { 20 | "in": "query", 21 | "name": "include", 22 | "required": false, 23 | "type": "string" 24 | }, 25 | { 26 | "in": "query", 27 | "name": "exclude", 28 | "required": false, 29 | "type": "string" 30 | } 31 | ] 32 | }, 33 | "post": { 34 | "consumes": ["application/x-www-form-urlencoded"], 35 | "parameters": [ 36 | { 37 | "in": "body", 38 | "name": "include", 39 | "required": false, 40 | "type": "string" 41 | }, 42 | { 43 | "in": "body", 44 | "name": "exclude", 45 | "required": false, 46 | "type": "string" 47 | } 48 | ] 49 | } 50 | }, 51 | "/repositories/{workspace}/{repo_slug}/commits/{revision}": { 52 | "get": { 53 | "parameters": [ 54 | { 55 | "in": "query", 56 | "name": "include", 57 | "required": false, 58 | "type": "string" 59 | }, 60 | { 61 | "in": "query", 62 | "name": "exclude", 63 | "required": false, 64 | "type": "string" 65 | } 66 | ] 67 | }, 68 | "post": { 69 | "parameters": [ 70 | { 71 | "in": "body", 72 | "name": "include", 73 | "required": false, 74 | "type": "string" 75 | }, 76 | { 77 | "in": "body", 78 | "name": "exclude", 79 | "required": false, 80 | "type": "string" 81 | } 82 | ] 83 | } 84 | }, 85 | "/repositories/{workspace}/{repo_slug}/deploy-keys": { 86 | "post": { 87 | "parameters": [ 88 | { 89 | "in": "body", 90 | "name": "key", 91 | "required": true, 92 | "type": "string" 93 | }, 94 | { 95 | "in": "body", 96 | "name": "label", 97 | "required": true, 98 | "type": "string" 99 | } 100 | ] 101 | } 102 | }, 103 | "/repositories/{workspace}/{repo_slug}/downloads": { 104 | "post": { 105 | "consumes": ["multipart/form-data"], 106 | "parameters": [ 107 | { 108 | "in": "body", 109 | "name": "_body", 110 | "required": true 111 | } 112 | ] 113 | } 114 | }, 115 | "/repositories/{workspace}/{repo_slug}/filehistory/{commit}/{path}": { 116 | "get": { 117 | "parameters": [ 118 | { 119 | "in": "query", 120 | "name": "fields", 121 | "required": false, 122 | "type": "string" 123 | } 124 | ] 125 | } 126 | }, 127 | "/repositories/{workspace}/{repo_slug}/hooks": { 128 | "post": { 129 | "parameters": [ 130 | { 131 | "in": "body", 132 | "name": "_body", 133 | "required": true 134 | } 135 | ] 136 | } 137 | }, 138 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}": { 139 | "put": { 140 | "parameters": [ 141 | { 142 | "in": "body", 143 | "name": "_body", 144 | "required": true, 145 | "schema": { 146 | "$ref": "#/definitions/issue" 147 | } 148 | } 149 | ] 150 | } 151 | }, 152 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/attachments": { 153 | "post": { 154 | "consumes": ["multipart/form-data"], 155 | "parameters": [ 156 | { 157 | "in": "body", 158 | "name": "_body", 159 | "required": true 160 | } 161 | ] 162 | } 163 | }, 164 | "/repositories/{workspace}/{repo_slug}/refs/branches": { 165 | "post": { 166 | "parameters": [ 167 | { 168 | "in": "body", 169 | "name": "_body", 170 | "required": true 171 | } 172 | ] 173 | } 174 | }, 175 | "/repositories/{workspace}/{repo_slug}/src": { 176 | "post": { 177 | "consumes": ["multipart/form-data", "application/x-www-form-urlencoded"], 178 | "parameters": [ 179 | { 180 | "in": "body", 181 | "name": "_body", 182 | "required": false 183 | }, 184 | { 185 | "in": "body", 186 | "name": "author", 187 | "type": "string" 188 | }, 189 | { 190 | "in": "body", 191 | "name": "branch", 192 | "type": "string" 193 | }, 194 | { 195 | "in": "body", 196 | "name": "files", 197 | "type": "string" 198 | }, 199 | { 200 | "in": "body", 201 | "name": "message", 202 | "type": "string" 203 | }, 204 | { 205 | "in": "body", 206 | "name": "parents", 207 | "type": "string" 208 | } 209 | ] 210 | } 211 | }, 212 | "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}/changes/": { 213 | "post": { 214 | "parameters": [ 215 | { 216 | "in": "body", 217 | "name": "_body", 218 | "required": true 219 | } 220 | ] 221 | } 222 | }, 223 | "/snippets/{workspace}/{encoded_id}/watch": { 224 | "delete": { 225 | "responses": { 226 | "204": { 227 | "schema": { 228 | "$ref": "" 229 | } 230 | } 231 | } 232 | }, 233 | "get": { 234 | "responses": { 235 | "204": { 236 | "schema": { 237 | "$ref": "" 238 | } 239 | } 240 | } 241 | }, 242 | "put": { 243 | "responses": { 244 | "204": { 245 | "schema": { 246 | "$ref": "" 247 | } 248 | } 249 | } 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /scripts/generate-types.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const { readFileSync, writeFileSync } = require('fs') 4 | const camelCase = require('lodash/camelCase') 5 | const toPairs = require('lodash/toPairs') 6 | const { render: renderTemplate } = require('mustache') 7 | const { 8 | join: joinPath, 9 | relative: relativePath, 10 | resolve: resolvePath, 11 | } = require('path') 12 | const prettier = require('prettier') 13 | 14 | const { isPaginatedEndpoint } = require('./utils/is-paginated-endpoint') 15 | const { pascalCase } = require('./utils/pascal-case') 16 | 17 | const ROUTES = require('../src/plugins/register-api-endpoints/routes.json') 18 | const typesSchema = require('../templates/types-schema.json') 19 | 20 | async function getTypesBlob(languageName) { 21 | const compileSchema = { 22 | typescript: require('json-schema-to-typescript').compile, 23 | }[languageName.toLowerCase()] 24 | 25 | const typesBlob = await compileSchema(typesSchema, 'RootInterfaceToDiscard', { 26 | bannerComment: false, 27 | }) 28 | 29 | const typesBlobWithoutRootInterface = typesBlob.replace( 30 | /interface RootInterfaceToDiscard(?:\s|.)+?export /, 31 | '' 32 | ) 33 | 34 | const typesBlobWithIndentation = typesBlobWithoutRootInterface 35 | .split('\n') 36 | .filter(Boolean) 37 | .join('\n ') 38 | 39 | return typesBlobWithIndentation 40 | } 41 | 42 | const typeMap = { 43 | integer: 'number', 44 | } 45 | 46 | const bodyTypeMap = { 47 | default: 'Schema.Any', 48 | 'application/x-www-form-urlencoded': 'Schema.AnyObject', 49 | 'multipart/form-data': 'FormData', 50 | } 51 | 52 | function parameterize(paramName, param, accepts) { 53 | if (!param) return {} 54 | 55 | let type = typeMap[param.type] || param.type 56 | 57 | const enums = param.enum ? param.enum.map(JSON.stringify).join(' | ') : null 58 | 59 | let schema = false 60 | if (param.schema) { 61 | schema = true 62 | type = param.schema 63 | } 64 | 65 | if (accepts && paramName === '_body') { 66 | type = accepts 67 | .map((type) => bodyTypeMap[type] || bodyTypeMap.default) 68 | .join(' | ') 69 | } 70 | 71 | return { 72 | name: paramName, 73 | required: param.required, 74 | schema, 75 | type: enums || type, 76 | } 77 | } 78 | 79 | async function generateTypes(languageName, templateFile) { 80 | const template = readFileSync(templateFile, 'utf8') 81 | 82 | const typesBlob = await getTypesBlob(languageName) 83 | 84 | const namespaces = Object.keys(ROUTES).reduce((namespaces, namespaceName) => { 85 | const endpoints = toPairs(ROUTES[namespaceName]).reduce( 86 | (endpoints, [endpointName, endpointObject]) => { 87 | const namespacedParamsName = pascalCase( 88 | `${namespaceName}-${endpointName}` 89 | ) 90 | 91 | if (endpointObject.alias) { 92 | const [namespaceAlias, endpointAlias] = endpointObject.alias.split( 93 | '.' 94 | ) 95 | endpointObject = ROUTES[namespaceAlias][endpointAlias] 96 | } 97 | 98 | const isPaginated = isPaginatedEndpoint(endpointName, endpointObject) 99 | 100 | if (isPaginated) { 101 | endpointObject.params = Object.assign({}, endpointObject.params, { 102 | page: { require: false, type: 'string' }, 103 | pagelen: { required: false, type: 'integer' }, 104 | q: { required: false, type: 'string' }, 105 | sort: { required: false, type: 'string' }, 106 | }) 107 | } 108 | 109 | if (endpointObject.method === 'GET') { 110 | endpointObject.params = Object.assign({}, endpointObject.params, { 111 | fields: { require: false, type: 'string' }, 112 | }) 113 | } 114 | 115 | const params = toPairs(endpointObject.params).reduce( 116 | (params, [paramName, param]) => 117 | params.concat( 118 | parameterize(paramName, param, endpointObject.accepts) 119 | ), 120 | [] 121 | ) 122 | 123 | const hasParams = params.length > 0 124 | const paramsType = hasParams 125 | ? namespacedParamsName 126 | : pascalCase('Empty') 127 | const responseType = endpointObject.returns || 'Any' 128 | 129 | return endpoints.concat({ 130 | name: camelCase(endpointName), 131 | params, 132 | paramsType, 133 | responseType, 134 | exclude: !hasParams, 135 | }) 136 | }, 137 | [] 138 | ) 139 | 140 | return namespaces.concat({ 141 | namespace: namespaceName, 142 | endpoints, 143 | }) 144 | }, []) 145 | 146 | const types = renderTemplate(template, { 147 | namespaces, 148 | typesBlob, 149 | }) 150 | 151 | const prettyTypes = prettier.format(types, { 152 | parser: languageName.toLowerCase(), 153 | semi: false, 154 | singleQuote: true, 155 | }) 156 | 157 | return prettyTypes 158 | } 159 | 160 | function writeTypes(languageName, outputFile, types) { 161 | writeFileSync(outputFile, types, 'utf8') 162 | 163 | console.log( 164 | `${languageName} types written to: ${relativePath( 165 | resolvePath('.'), 166 | outputFile 167 | )}` 168 | ) 169 | } 170 | 171 | const templatesPath = resolvePath('templates') 172 | const typesPath = resolvePath('lib') 173 | 174 | const typeFiles = [ 175 | { 176 | languageName: 'TypeScript', 177 | templateFile: joinPath(templatesPath, 'bitbucket.d.ts.mustache'), 178 | outputFile: joinPath(typesPath, 'bitbucket.d.ts'), 179 | }, 180 | { 181 | languageName: 'TypeScript', 182 | templateFile: joinPath(templatesPath, 'index.d.ts.mustache'), 183 | outputFile: joinPath(typesPath, 'index.d.ts'), 184 | }, 185 | { 186 | languageName: 'TypeScript', 187 | templateFile: joinPath(templatesPath, 'minimal.d.ts.mustache'), 188 | outputFile: joinPath(typesPath, 'minimal.d.ts'), 189 | }, 190 | { 191 | languageName: 'TypeScript', 192 | templateFile: joinPath(templatesPath, 'plugins/authenticate.d.ts.mustache'), 193 | outputFile: joinPath(typesPath, 'plugins/authenticate.d.ts'), 194 | }, 195 | ] 196 | 197 | typeFiles.forEach(({ languageName, templateFile, outputFile }) => { 198 | generateTypes(languageName, templateFile, outputFile).then( 199 | writeTypes.bind(null, languageName, outputFile) 200 | ) 201 | }) 202 | -------------------------------------------------------------------------------- /specification/tags.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "The addon resource is intended to use used by Bitbucket Cloud Connect\nApps, and only supports JWT authentication.\n", 4 | "name": "Addon" 5 | }, 6 | { 7 | "description": "Repository owners and administrators can set branch management\nrules on a repository that control what can be pushed by whom.\nThrough these rules, you can enforce a project or team\nworkflow. For example, owners or administrators can:\n\n* Limit push powers\n* Prevent branch deletion\n* Prevent history re-writes (Git only)\n", 8 | "name": "Branch restrictions" 9 | }, 10 | { 11 | "description": "The branching model resource is used to modify the branching model\nfor a repository.\n\nYou can use the branching model to define a branch based workflow\nfor your repositories. When you map your workflow to branch types,\nyou can ensure that branches are named consistently by configuring\nwhich branch types to make available.\n", 12 | "name": "Branching model" 13 | }, 14 | { 15 | "description": "Commit statuses provide a way to tag commits with meta data,\nlike automated build results.\n", 16 | "name": "Commit statuses" 17 | }, 18 | { 19 | "description": "These are the repository's commits. They are paginated and returned in\nreverse chronological order, similar to the output of git log.\n", 20 | "name": "Commits" 21 | }, 22 | { 23 | "description": "Teams are deploying code faster than ever, thanks to continuous\ndelivery practices and tools like Bitbucket Pipelines. Bitbucket\nDeployments gives teams visibility into their deployment\nenvironments and helps teams to track how far changes have\nprogressed in their deployment pipeline.\n", 24 | "name": "Deployments" 25 | }, 26 | { 27 | "description": "Access the list of download links associated with the repository.", 28 | "name": "Downloads" 29 | }, 30 | { 31 | "description": "The issue resources provide functionality for getting information on\nissues in an issue tracker, creating new issues, updating them and deleting\nthem.\n\nYou can access public issues without authentication, but you can't gain access\nto private repositories' issues. By authenticating, you will get the ability\nto create issues, as well as access to updating data or deleting issues you\nhave access to. Issue Tracker features are not supported for repositories in workspaces administered through admin.atlassian.com.\n", 32 | "name": "Issue tracker" 33 | }, 34 | { 35 | "description": "Bitbucket Pipelines brings continuous delivery to Bitbucket\nCloud, empowering teams with full branching to deployment\nvisibility and faster feedback loops.\n", 36 | "name": "Pipelines" 37 | }, 38 | { 39 | "description": "Bitbucket Cloud projects make it easier for teams to focus on\na goal, product, or process by organizing their repositories.\n", 40 | "name": "Projects" 41 | }, 42 | { 43 | "description": "Pull requests are a feature that makes it easier for developers\nto collaborate using Bitbucket. They provide a user-friendly web\ninterface for discussing proposed changes before integrating them\ninto the official project.\n", 44 | "name": "Pullrequests" 45 | }, 46 | { 47 | "description": "The refs resource allows you access branches and tags in a repository.\nBy default, results will be in the order the underlying source control\nsystem returns them and identical to the ordering one sees when running\n\"$ git show-ref\". Note that this follows simple lexical ordering of the\n ref names.\n", 48 | "name": "Refs" 49 | }, 50 | { 51 | "description": "Code insights provides reports, annotations, and metrics to help you\nand your team improve code quality in pull requests throughout the code\nreview process. Some of the available code insights are static analysis\nreports, security scan results, artifact links, unit tests, and build\nstatus.\n", 52 | "name": "Reports" 53 | }, 54 | { 55 | "description": "A Git repository is a virtual storage of your project. It\nallows you to save versions of your code, which you can access\nwhen needed. The repo resource allows you to access public repos,\nor repos that belong to a specific workspace.\n", 56 | "name": "Repositories" 57 | }, 58 | { 59 | "description": "Snippets allow you share code segments or files with yourself, members of\nyour workspace, or the world.\n\nLike pull requests, repositories and workspaces, the full set of snippets\nis defined by what the current user has access to. This includes all\nsnippets owned by any of the workspaces the user is a member of, or\nsnippets by other users that the current user is either watching or has\n collaborated on (for instance by commenting on it).\n", 60 | "name": "Snippets" 61 | }, 62 | { 63 | "description": "Browse the source code in the repository and\n create new commits by uploading.", 64 | "name": "Source" 65 | }, 66 | { 67 | "description": "The SSH resource allows you to manage SSH keys.\n", 68 | "name": "Ssh" 69 | }, 70 | { 71 | "description": "The teams resource has been deprecated, and the workspaces\nendpoint should be used instead.\n\nThe teams resource returns all the teams that the authenticated\nuser is associated with.\n", 72 | "name": "Teams" 73 | }, 74 | { 75 | "description": "The users resource allows you to access public information\nassociated with a user account. Most resources in the users\nendpoint have been deprecated in favor of workspaces.\n", 76 | "name": "Users" 77 | }, 78 | { 79 | "description": "Webhooks provide a way to configure Bitbucket Cloud to make requests to\nyour server (or another external service) whenever certain events occur in\nBitbucket Cloud.\n\nA webhook consists of:\n\n* A subject -- The resource that generates the events. Currently, this resource\nis the repository, user account, or team where you create the webhook.\n* One or more event -- The default event is a repository push, but you can\nselect multiple events that can trigger the webhook.\n* A URL -- The endpoint where you want Bitbucket to send the event payloads\nwhen a matching event happens.\n\nThere are two parts to getting a webhook to work: creating the webhook and\ntriggering the webhook. After you create a webhook for an event, every time\nthat event occurs, Bitbucket sends a payload request that describes the event\nto the specified URL. Thus, you can think of webhooks as a kind of\nnotification system.\n\nUse webhooks to integrate applications with Bitbucket Cloud. The following\nuse cases provides examples of when you would want to use webhooks:\n\n* Every time a user pushes commits in a repository, you may want to notify\nyour CI server to start a build.\n* Every time a user pushes commits or creates a pull request, you may want to\ndisplay a notification in your application.\n", 80 | "name": "Webhooks" 81 | }, 82 | { 83 | "description": "The wiki is a simple place to keep documents. Some people use it\nas their project home page. The wiki is a Git repository, so you\ncan clone it and edit it like any other source files.\n", 84 | "name": "Wiki" 85 | }, 86 | { 87 | "description": "A workspace is where you create repositories, collaborate on\nyour code, and organize different streams of work in your Bitbucket\nCloud account. Workspaces replace the use of teams and users in API\ncalls.\n", 88 | "name": "Workspaces" 89 | } 90 | ] 91 | -------------------------------------------------------------------------------- /scripts/generate-api-docs.js: -------------------------------------------------------------------------------- 1 | const { find, get, keys, lowerCase, toPairs, upperCase } = require('lodash') 2 | const { resolve: resolvePath } = require('path') 3 | const { writeFileSync } = require('fs') 4 | 5 | const { isPaginatedEndpoint } = require('./utils/is-paginated-endpoint') 6 | 7 | const PATHS_SPEC = require('../specification/paths.json') 8 | // const PATHS_SPEC_EXTRAS = require('../specification/extras/paths.json') 9 | 10 | const docsPath = resolvePath('docs') 11 | 12 | const ROUTES = require('../src/plugins/register-api-endpoints/routes.json') 13 | 14 | // One of many workarounds for Bitbucket's faulty API Specification 15 | const URL_ALIASES = {} 16 | 17 | const usernameRegex = /\/\{username\}\// 18 | 19 | const getAPIDescription = ({ method, url }, apiName, namespaceName) => { 20 | method = method.toLowerCase() 21 | url = URL_ALIASES[url] || url 22 | 23 | const spec = get( 24 | PATHS_SPEC, 25 | url, 26 | usernameRegex.test(url) 27 | ? get(PATHS_SPEC, url.replace(usernameRegex, '/{workspace}/')) 28 | : null 29 | ) 30 | 31 | return get(spec, [method, 'description'], get(spec, [method, 'summary'], '')) 32 | } 33 | 34 | const getAPIParamDefault = (param, paramName, { method, url }) => { 35 | method = method.toLowerCase() 36 | url = URL_ALIASES[url] || url 37 | 38 | url = url.replace(/\{\?.+\}$/, '') 39 | 40 | const spec = get( 41 | PATHS_SPEC, 42 | url, 43 | usernameRegex.test(url) 44 | ? get(PATHS_SPEC, url.replace(usernameRegex, '/{workspace}/')) 45 | : null 46 | ) 47 | 48 | let defaultVal = 49 | (find(spec[method].parameters, ['name', paramName]) || {}).default || null 50 | 51 | if (defaultVal === null) { 52 | defaultVal = (find(spec.parameters, ['name', paramName]) || {}).default 53 | } 54 | 55 | return defaultVal || null 56 | } 57 | 58 | const getAPIParamDescription = (param, paramName, { method, url }) => { 59 | method = method.toLowerCase() 60 | url = URL_ALIASES[url] || url 61 | 62 | url = url.replace(/\{\?.+\}$/, '') 63 | 64 | const spec = get( 65 | PATHS_SPEC, 66 | url, 67 | usernameRegex.test(url) 68 | ? get(PATHS_SPEC, url.replace(usernameRegex, '/{workspace}/')) 69 | : null 70 | ) 71 | 72 | let description = (find(spec[method].parameters, ['name', paramName]) || {}) 73 | .description 74 | 75 | if (!description) { 76 | description = (find(spec.parameters, ['name', paramName]) || {}).description 77 | } 78 | 79 | description = description || '' 80 | 81 | description = description.replace( 82 | /\[filtering and sorting\]\(.+\)/, 83 | '[filtering and sorting](https://developer.atlassian.com/bitbucket/api/2/reference/meta/filtering)' 84 | ) 85 | 86 | return description 87 | } 88 | 89 | const toAPIParamComment = (param, paramName, api) => { 90 | const paramDescription = getAPIParamDescription(param, paramName, api) 91 | const paramDefaultVal = getAPIParamDefault(param, paramName, api) 92 | const paramType = param.type 93 | 94 | let paramLabel = paramName 95 | if (paramDefaultVal !== null) { 96 | paramLabel += `="${paramDefaultVal}"` 97 | } 98 | 99 | const paramRequired = Boolean(param.required) 100 | if (!paramRequired) { 101 | paramLabel = `[${paramLabel}]` 102 | } 103 | 104 | const paramGroup = `Parameters` 105 | 106 | let allowedValues = '' 107 | if (param.enum) { 108 | allowedValues = `=${param.enum.join(',')}` 109 | } 110 | 111 | return ` * @apiParam (${paramGroup}) {${paramType}${allowedValues}} ${paramLabel 112 | .replace(/\./g, ':') 113 | .replace(/\[\]/g, '')} ${paramDescription.split('\n\n')[0]}` 114 | } 115 | 116 | const getLinkToOfficialDocs = ({ method, url }, apiName, namespaceName) => { 117 | url = URL_ALIASES[url] || url 118 | 119 | url = url.replace(/\{\?.+\}$/, '') 120 | 121 | const specUrl = get(PATHS_SPEC, url) 122 | ? url 123 | : usernameRegex.test(url) 124 | ? get(PATHS_SPEC, url.replace(usernameRegex, '/{workspace}/')) 125 | ? url.replace(usernameRegex, '/{workspace}/') 126 | : null 127 | : null 128 | 129 | if (!specUrl) { 130 | throw new Error(`specUrl missing for url: ${url}`) 131 | } 132 | 133 | return `[API Docs]` 136 | } 137 | 138 | const toAPIComment = (api, apiName, namespaceName) => { 139 | if (api.alias) { 140 | const [namespaceAlias, apiAlias] = api.alias.split('.') 141 | api = ROUTES[namespaceAlias][apiAlias] 142 | } 143 | 144 | const method = api.method 145 | const url = api.url 146 | let params = api.params 147 | 148 | if (!method) { 149 | throw new Error( 150 | `HTTP method missing for ${namespaceName}.${apiName} in routes.json` 151 | ) 152 | } 153 | 154 | const isPaginated = isPaginatedEndpoint(apiName, api) 155 | 156 | if (isPaginated) { 157 | params = Object.assign({}, params, { 158 | page: { require: false, type: 'string' }, 159 | pagelen: { required: false, type: 'integer' }, 160 | q: { required: false, type: 'string' }, 161 | sort: { required: false, type: 'string' }, 162 | }) 163 | } 164 | 165 | if (method === 'GET') { 166 | params = Object.assign({}, params, { 167 | fields: { require: false, type: 'string' }, 168 | }) 169 | } 170 | 171 | const descriptionWithLinkToOfficialDocs = [ 172 | getAPIDescription(api, apiName, namespaceName).split('\n\n')[0], 173 | getLinkToOfficialDocs(api, apiName, namespaceName), 174 | ] 175 | .filter(Boolean) 176 | .join(' ') 177 | 178 | const commentLines = [ 179 | `/**`, 180 | ` * @api {${upperCase(method)}} ${url} ${apiName}`, 181 | ` * @apiName ${namespaceName}.${apiName}`, 182 | ` * @apiDescription ${descriptionWithLinkToOfficialDocs}`, 183 | ` * @apiGroup ${namespaceName}`, 184 | ' *', 185 | ].concat( 186 | toPairs(params).map(([paramName, param]) => 187 | toAPIParamComment(param, paramName, api) 188 | ) 189 | ) 190 | 191 | const paramsString = keys(params).join(', ') 192 | 193 | return commentLines 194 | .concat([ 195 | ` * @apiExample {js} async/await`, 196 | ` * const { data, headers } = await bitbucket.${namespaceName}.${apiName}({ ${paramsString} })`, 197 | ` * @apiExample {js} Promise`, 198 | ` * bitbucket.${namespaceName}.${apiName}({ ${paramsString} }).then(({ data, headers }) => {})`, 199 | ` */`, 200 | ]) 201 | .join('\n') 202 | } 203 | 204 | const prepareAPI = (api, apiName, namespaceName) => 205 | toAPIComment(api, apiName, namespaceName) 206 | 207 | const toSectionComment = (namespaceName) => ` 208 | /** 209 | * ${namespaceName} 210 | * @namespace ${namespaceName} 211 | */ 212 | ` 213 | 214 | const prepareNamespace = (namespace, namespaceName) => 215 | [toSectionComment(namespaceName)] 216 | .concat( 217 | keys(namespace).map((apiName) => 218 | prepareAPI(namespace[apiName], apiName, namespaceName) 219 | ) 220 | ) 221 | .join('\n') 222 | 223 | const apiDocs = keys(ROUTES) 224 | .map((namespaceName) => 225 | prepareNamespace(ROUTES[namespaceName], namespaceName) 226 | ) 227 | .join('\n') 228 | .trim() 229 | 230 | writeFileSync(resolvePath(docsPath, 'apidoc.js'), `${apiDocs}\n`) 231 | -------------------------------------------------------------------------------- /scripts/generate-routes/index.js: -------------------------------------------------------------------------------- 1 | const deepclean = require('clean-deep') 2 | const deepsort = require('deep-sort-object') 3 | const get = require('lodash/get') 4 | const path = require('path') 5 | 6 | const { 7 | extractEndpointNamesForNamespace, 8 | } = require('../utils/extract-endpoint-names-for-namespace') 9 | const { 10 | extractNamespaceFromURL, 11 | } = require('../utils/extract-namespace-from-url') 12 | const { extractNamespaceNames } = require('../utils/extract-namespace-names') 13 | const { getDuplicates } = require('../utils/get-duplicates') 14 | const { isPaginatedEndpoint } = require('../utils/is-paginated-endpoint') 15 | const { writeToFile } = require('../utils/write-to-file') 16 | 17 | const PATHS_SPEC = require('../../specification/paths.json') 18 | const PATHS_SPEC_EXTRAS = require('../../specification/extras/paths.json') 19 | const ENDPOINT_NAMES = require('../../specification/extras/endpoint-names.json') 20 | 21 | const routesPath = path.resolve( 22 | 'src/plugins/register-api-endpoints/routes.json' 23 | ) 24 | 25 | const { processConsumes } = require('./process-consumes') 26 | const { processDeprecated } = require('./process-deprecated') 27 | const { processParameters } = require('./process-parameters') 28 | const { processProduces } = require('./process-produces') 29 | const { processResponses } = require('./process-responses') 30 | const { setAlias } = require('./set-alias') 31 | const { setMethod } = require('./set-method') 32 | const { setUrl } = require('./set-url') 33 | 34 | function initializeRoutes(routesObject) { 35 | const namespaceNames = extractNamespaceNames(ENDPOINT_NAMES) 36 | 37 | namespaceNames.forEach((namespaceName) => { 38 | routesObject[namespaceName] = {} 39 | 40 | const endpointNames = extractEndpointNamesForNamespace( 41 | ENDPOINT_NAMES, 42 | namespaceName 43 | ) 44 | 45 | const duplicateEndpointNames = getDuplicates(endpointNames) 46 | if (duplicateEndpointNames.length) { 47 | throw new Error( 48 | `namespace:[${namespaceName}] contains duplicate endpoint names: [${duplicateEndpointNames}]` 49 | ) 50 | } 51 | 52 | endpointNames.forEach((methodName) => { 53 | routesObject[namespaceName][methodName] = { 54 | params: {}, 55 | } 56 | }) 57 | }) 58 | } 59 | 60 | function populateRoutes(routesObject) { 61 | const usernameRegex = /\/\{username\}\// 62 | const workspaceRegex = /\/\{workspace\}\// 63 | 64 | for (const [url, methods] of Object.entries(ENDPOINT_NAMES)) { 65 | // if spec for */{username}/* path does not exist 66 | // then spec for */{workspace}/* path will be tried 67 | 68 | const spec = get( 69 | PATHS_SPEC, 70 | url, 71 | usernameRegex.test(url) 72 | ? get(PATHS_SPEC, url.replace(usernameRegex, '/{workspace}/')) 73 | : workspaceRegex.test(url) 74 | ? get(PATHS_SPEC, url.replace(workspaceRegex, '/{username}/')) 75 | : null 76 | ) 77 | 78 | const specExtras = get( 79 | PATHS_SPEC_EXTRAS, 80 | url, 81 | usernameRegex.test(url) 82 | ? get(PATHS_SPEC_EXTRAS, url.replace(usernameRegex, '/{workspace}/')) 83 | : workspaceRegex.test(url) 84 | ? get(PATHS_SPEC_EXTRAS, url.replace(workspaceRegex, '/{username}/')) 85 | : null 86 | ) 87 | 88 | for (const [method, namespaces] of Object.entries(methods)) { 89 | for (const [namespaceName, endpointName] of Object.entries(namespaces)) { 90 | // ignore endpoint with empty name 91 | if (!endpointName) continue 92 | 93 | const isAlias = setAlias( 94 | routesObject[namespaceName][endpointName], 95 | namespaceName, 96 | extractNamespaceFromURL(url), 97 | get(ENDPOINT_NAMES, [url, method]) 98 | ) 99 | 100 | // ignore further processing for alias endpoint 101 | if (isAlias) continue 102 | 103 | setMethod(routesObject[namespaceName][endpointName], method) 104 | setUrl(routesObject[namespaceName][endpointName], url) 105 | 106 | processParameters(routesObject[namespaceName][endpointName], spec, url) 107 | 108 | const methodSpec = get(spec, method) 109 | if (methodSpec) { 110 | processConsumes(routesObject[namespaceName][endpointName], methodSpec) 111 | processProduces(routesObject[namespaceName][endpointName], methodSpec) 112 | processParameters( 113 | routesObject[namespaceName][endpointName], 114 | methodSpec, 115 | url 116 | ) 117 | processResponses( 118 | routesObject[namespaceName][endpointName], 119 | methodSpec 120 | ) 121 | processDeprecated( 122 | routesObject[namespaceName][endpointName], 123 | methodSpec 124 | ) 125 | } 126 | 127 | const methodSpecExtras = get(specExtras, method) 128 | if (methodSpecExtras) { 129 | processConsumes( 130 | routesObject[namespaceName][endpointName], 131 | methodSpecExtras 132 | ) 133 | processProduces( 134 | routesObject[namespaceName][endpointName], 135 | methodSpecExtras 136 | ) 137 | processParameters( 138 | routesObject[namespaceName][endpointName], 139 | methodSpecExtras, 140 | url 141 | ) 142 | processResponses( 143 | routesObject[namespaceName][endpointName], 144 | methodSpecExtras 145 | ) 146 | processDeprecated( 147 | routesObject[namespaceName][endpointName], 148 | methodSpecExtras 149 | ) 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | function formatUrls(routesObject) { 157 | for (const [, endpoints] of Object.entries(routesObject)) { 158 | for (const [endpointName, endpointObject] of Object.entries(endpoints)) { 159 | // ignore alias endpoint 160 | if (endpointObject.alias) continue 161 | 162 | // ignore endpoint with method GET/DELETE 163 | if (['GET', 'DELETE'].includes(endpointObject.method)) continue 164 | 165 | if (isPaginatedEndpoint(endpointName, endpointObject)) { 166 | processParameters( 167 | endpointObject, 168 | { 169 | parameters: [ 170 | { in: 'query', name: 'page', require: false, type: 'string' }, 171 | { 172 | in: 'query', 173 | name: 'pagelen', 174 | required: false, 175 | type: 'integer', 176 | }, 177 | { in: 'query', name: 'q', required: false, type: 'string' }, 178 | { in: 'query', name: 'sort', required: false, type: 'string' }, 179 | ], 180 | }, 181 | endpointObject.url 182 | ) 183 | } 184 | 185 | const queryParams = Object.keys(endpointObject.params).filter( 186 | (paramName) => endpointObject.params[paramName].in === 'query' 187 | ) 188 | 189 | if (queryParams.length) { 190 | const queryTemplate = `{?${queryParams.join(',')}}` 191 | endpointObject.url = `${endpointObject.url}${queryTemplate}` 192 | } 193 | } 194 | } 195 | } 196 | 197 | function cleanupParams(routesObject) { 198 | const unnecessaryParamNames = ['page', 'pagelen', 'q', 'sort', 'fields'] 199 | 200 | for (const [, endpoints] of Object.entries(routesObject)) { 201 | for (const [, endpointObject] of Object.entries(endpoints)) { 202 | // ignore alias endpoint 203 | if (endpointObject.alias) continue 204 | 205 | // delete unnecessary params 206 | for (const paramName of Object.keys(endpointObject.params)) { 207 | if (unnecessaryParamNames.includes(paramName)) { 208 | delete endpointObject.params[paramName] 209 | } 210 | } 211 | 212 | // delete unnecessary properties from params 213 | for (const paramName of Object.keys(endpointObject.params)) { 214 | delete endpointObject.params[paramName].in 215 | } 216 | } 217 | } 218 | } 219 | 220 | function generateRoutes() { 221 | const routesObject = {} 222 | 223 | initializeRoutes(routesObject) 224 | populateRoutes(routesObject) 225 | formatUrls(routesObject) 226 | cleanupParams(routesObject) 227 | 228 | return routesObject 229 | } 230 | 231 | writeToFile( 232 | routesPath, 233 | JSON.stringify(deepsort(deepclean(generateRoutes())), null, 2) 234 | ).catch((err) => { 235 | console.error(err) 236 | process.exit(1) 237 | }) 238 | -------------------------------------------------------------------------------- /specification/extras/endpoint-names.json: -------------------------------------------------------------------------------- 1 | { 2 | "/addon": { 3 | "delete": { 4 | "addon": "" 5 | }, 6 | "put": { 7 | "addon": "" 8 | } 9 | }, 10 | "/addon/linkers": { 11 | "get": { 12 | "addon": "" 13 | } 14 | }, 15 | "/addon/linkers/{linker_key}": { 16 | "get": { 17 | "addon": "" 18 | } 19 | }, 20 | "/addon/linkers/{linker_key}/values": { 21 | "delete": { 22 | "addon": "" 23 | }, 24 | "get": { 25 | "addon": "" 26 | }, 27 | "post": { 28 | "addon": "" 29 | }, 30 | "put": { 31 | "addon": "" 32 | } 33 | }, 34 | "/addon/linkers/{linker_key}/values/{value_id}": { 35 | "delete": { 36 | "addon": "" 37 | }, 38 | "get": { 39 | "addon": "" 40 | } 41 | }, 42 | "/hook_events": { 43 | "get": { 44 | "hook_events": "getAllSubjectTypes", 45 | "webhooks": "getAllSubjectTypes" 46 | } 47 | }, 48 | "/hook_events/{subject_type}": { 49 | "get": { 50 | "hook_events": "list", 51 | "webhooks": "list" 52 | } 53 | }, 54 | "/pullrequests/{selected_user}": { 55 | "get": { 56 | "pullrequests": "listPullrequestsForUser" 57 | } 58 | }, 59 | "/repositories": { 60 | "get": { 61 | "repositories": "listGlobal" 62 | } 63 | }, 64 | "/repositories/{workspace}": { 65 | "get": { 66 | "repositories": "list" 67 | } 68 | }, 69 | "/repositories/{workspace}/{repo_slug}": { 70 | "delete": { 71 | "repositories": "delete" 72 | }, 73 | "get": { 74 | "repositories": "get" 75 | }, 76 | "post": { 77 | "repositories": "create" 78 | }, 79 | "put": { 80 | "repositories": "update" 81 | } 82 | }, 83 | "/repositories/{workspace}/{repo_slug}/branch-restrictions": { 84 | "get": { 85 | "branchrestrictions": "list", 86 | "repositories": "listBranchRestrictions" 87 | }, 88 | "post": { 89 | "branchrestrictions": "create", 90 | "repositories": "createBranchRestriction" 91 | } 92 | }, 93 | "/repositories/{workspace}/{repo_slug}/branch-restrictions/{id}": { 94 | "delete": { 95 | "branchrestrictions": "delete", 96 | "repositories": "deleteBranchRestriction" 97 | }, 98 | "get": { 99 | "branchrestrictions": "get", 100 | "repositories": "getBranchRestriction" 101 | }, 102 | "put": { 103 | "branchrestrictions": "update", 104 | "repositories": "updateBranchRestriction" 105 | } 106 | }, 107 | "/repositories/{workspace}/{repo_slug}/branching-model": { 108 | "get": { 109 | "branching_model": "get", 110 | "repositories": "getBranchingModel" 111 | } 112 | }, 113 | "/repositories/{workspace}/{repo_slug}/branching-model/settings": { 114 | "get": { 115 | "branching_model": "getSettings", 116 | "repositories": "getBranchingModelSettings" 117 | }, 118 | "put": { 119 | "branching_model": "updateSettings", 120 | "repositories": "updateBranchingModelSettings" 121 | } 122 | }, 123 | "/repositories/{workspace}/{repo_slug}/commit/{commit}": { 124 | "get": { 125 | "commits": "get", 126 | "repositories": "getCommit" 127 | } 128 | }, 129 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/approve": { 130 | "delete": { 131 | "commits": "deleteApproval", 132 | "repositories": "deleteCommitApproval" 133 | }, 134 | "post": { 135 | "commits": "createApproval", 136 | "repositories": "createCommitApproval" 137 | } 138 | }, 139 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/comments": { 140 | "get": { 141 | "commits": "listComments", 142 | "repositories": "listCommitComments" 143 | }, 144 | "post": { 145 | "commits": "createComment", 146 | "repositories": "createCommitComment" 147 | } 148 | }, 149 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/comments/{comment_id}": { 150 | "delete": { 151 | "commits": "deleteComment", 152 | "repositories": "deleteCommitComment" 153 | }, 154 | "get": { 155 | "commits": "getComment", 156 | "repositories": "getCommitComment" 157 | }, 158 | "put": { 159 | "commits": "updateComment", 160 | "repositories": "updateCommitComment" 161 | } 162 | }, 163 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/properties/{app_key}/{property_name}": { 164 | "delete": { 165 | "properties": "deleteCommitHostedPropertyValue", 166 | "repositories": "deleteCommitHostedPropertyValue" 167 | }, 168 | "get": { 169 | "properties": "getCommitHostedPropertyValue", 170 | "repositories": "getCommitHostedPropertyValue" 171 | }, 172 | "put": { 173 | "properties": "updateCommitHostedPropertyValue", 174 | "repositories": "updateCommitHostedPropertyValue" 175 | } 176 | }, 177 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/pullrequests": { 178 | "get": { 179 | "pullrequests": "listForCommit", 180 | "repositories": "listPullrequestsForCommit" 181 | } 182 | }, 183 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports": { 184 | "get": { 185 | "commits": "getReportsForCommit", 186 | "reports": "getReportsForCommit", 187 | "repositories": "getReportsForCommit" 188 | } 189 | }, 190 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}": { 191 | "delete": { 192 | "commits": "deleteReport", 193 | "reports": "deleteReport", 194 | "repositories": "deleteReport" 195 | }, 196 | "get": { 197 | "commits": "getReport", 198 | "reports": "getReport", 199 | "repositories": "getReport" 200 | }, 201 | "put": { 202 | "commits": "createOrUpdateReport", 203 | "reports": "createOrUpdateReport", 204 | "repositories": "createOrUpdateReport" 205 | } 206 | }, 207 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations": { 208 | "get": { 209 | "commits": "getAnnotationsForReport", 210 | "reports": "getAnnotationsForReport", 211 | "repositories": "getAnnotationsForReport" 212 | }, 213 | "post": { 214 | "commits": "bulkCreateOrUpdateAnnotations", 215 | "reports": "bulkCreateOrUpdateAnnotations", 216 | "repositories": "bulkCreateOrUpdateAnnotations" 217 | } 218 | }, 219 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations/{annotationId}": { 220 | "delete": { 221 | "commits": "deleteAnnotation", 222 | "reports": "deleteAnnotation", 223 | "repositories": "deleteAnnotation" 224 | }, 225 | "get": { 226 | "commits": "getAnnotation", 227 | "reports": "getAnnotation", 228 | "repositories": "getAnnotation" 229 | }, 230 | "put": { 231 | "commits": "createOrUpdateAnnotation", 232 | "reports": "createOrUpdateAnnotation", 233 | "repositories": "createOrUpdateAnnotation" 234 | } 235 | }, 236 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses": { 237 | "get": { 238 | "commitstatuses": "list", 239 | "repositories": "listCommitStatuses" 240 | } 241 | }, 242 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses/build": { 243 | "post": { 244 | "commitstatuses": "createBuildStatus", 245 | "repositories": "createCommitBuildStatus" 246 | } 247 | }, 248 | "/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses/build/{key}": { 249 | "get": { 250 | "commitstatuses": "getBuildStatus", 251 | "repositories": "getCommitBuildStatus" 252 | }, 253 | "put": { 254 | "commitstatuses": "updateBuildStatus", 255 | "repositories": "updateCommitBuildStatus" 256 | } 257 | }, 258 | "/repositories/{workspace}/{repo_slug}/commits": { 259 | "get": { 260 | "commits": "list", 261 | "repositories": "listCommits" 262 | }, 263 | "post": { 264 | "commits": "listAlt", 265 | "repositories": "listCommitsAlt" 266 | } 267 | }, 268 | "/repositories/{workspace}/{repo_slug}/commits/{revision}": { 269 | "get": { 270 | "commits": "listAt", 271 | "repositories": "listCommitsAt" 272 | }, 273 | "post": { 274 | "commits": "listAtAlt", 275 | "repositories": "listCommitsAtAlt" 276 | } 277 | }, 278 | "/repositories/{workspace}/{repo_slug}/components": { 279 | "get": { 280 | "issue_tracker": "listComponents", 281 | "repositories": "listComponents" 282 | } 283 | }, 284 | "/repositories/{workspace}/{repo_slug}/components/{component_id}": { 285 | "get": { 286 | "issue_tracker": "getComponent", 287 | "repositories": "getIssueComponent" 288 | } 289 | }, 290 | "/repositories/{workspace}/{repo_slug}/default-reviewers": { 291 | "get": { 292 | "pullrequests": "listDefaultReviewers", 293 | "repositories": "listDefaultReviewers" 294 | } 295 | }, 296 | "/repositories/{workspace}/{repo_slug}/default-reviewers/{target_username}": { 297 | "delete": { 298 | "pullrequests": "deleteDefaultReviewer", 299 | "repositories": "deleteDefaultReviewer" 300 | }, 301 | "get": { 302 | "pullrequests": "getDefaultReviewer", 303 | "repositories": "getDefaultReviewer" 304 | }, 305 | "put": { 306 | "pullrequests": "addDefaultReviewer", 307 | "repositories": "addDefaultReviewer" 308 | } 309 | }, 310 | "/repositories/{workspace}/{repo_slug}/deploy-keys": { 311 | "get": { 312 | "deploy": "listKeys", 313 | "deployments": "", 314 | "repositories": "listDeployKeys" 315 | }, 316 | "post": { 317 | "deploy": "createKey", 318 | "deployments": "", 319 | "repositories": "createDeployKey" 320 | } 321 | }, 322 | "/repositories/{workspace}/{repo_slug}/deploy-keys/{key_id}": { 323 | "delete": { 324 | "deploy": "deleteKey", 325 | "deployments": "", 326 | "repositories": "deleteDeployKey" 327 | }, 328 | "get": { 329 | "deploy": "getKey", 330 | "deployments": "", 331 | "repositories": "getDeployKey" 332 | }, 333 | "put": { 334 | "deploy": "updateKey", 335 | "deployments": "", 336 | "repositories": "updateDeployKey" 337 | } 338 | }, 339 | "/repositories/{workspace}/{repo_slug}/deployments": { 340 | "get": { 341 | "deployments": "list", 342 | "repositories": "listDeployments" 343 | } 344 | }, 345 | "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables": { 346 | "get": { 347 | "pipelines": "listDeploymentVariables", 348 | "repositories": "listDeploymentVariables" 349 | }, 350 | "post": { 351 | "pipelines": "createDeploymentVariable", 352 | "repositories": "createDeploymentVariable" 353 | } 354 | }, 355 | "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables/{variable_uuid}": { 356 | "delete": { 357 | "pipelines": "deleteDeploymentVariable", 358 | "repositories": "deleteDeploymentVariable" 359 | }, 360 | "put": { 361 | "pipelines": "updateDeploymentVariable", 362 | "repositories": "updateDeploymentVariable" 363 | } 364 | }, 365 | "/repositories/{workspace}/{repo_slug}/deployments/{deployment_uuid}": { 366 | "get": { 367 | "deployments": "get", 368 | "repositories": "getDeployment" 369 | } 370 | }, 371 | "/repositories/{workspace}/{repo_slug}/diff/{spec}": { 372 | "get": { 373 | "commits": "getDiff", 374 | "repositories": "getDiff" 375 | } 376 | }, 377 | "/repositories/{workspace}/{repo_slug}/diffstat/{spec}": { 378 | "get": { 379 | "commits": "", 380 | "repositories": "listDiffStats" 381 | } 382 | }, 383 | "/repositories/{workspace}/{repo_slug}/downloads": { 384 | "get": { 385 | "downloads": "list", 386 | "repositories": "listDownloads" 387 | }, 388 | "post": { 389 | "downloads": "create", 390 | "repositories": "createDownload" 391 | } 392 | }, 393 | "/repositories/{workspace}/{repo_slug}/downloads/{filename}": { 394 | "delete": { 395 | "downloads": "delete", 396 | "repositories": "deleteDownload" 397 | }, 398 | "get": { 399 | "downloads": "get", 400 | "repositories": "getDownload" 401 | } 402 | }, 403 | "/repositories/{workspace}/{repo_slug}/effective-branching-model": { 404 | "get": { 405 | "branching_model": "", 406 | "repositories": "getEffectiveBranchingModel" 407 | } 408 | }, 409 | "/repositories/{workspace}/{repo_slug}/effective-default-reviewers": { 410 | "get": { 411 | "pullrequests": "", 412 | "repositories": "listEffectiveDefaultReviewers" 413 | } 414 | }, 415 | "/repositories/{workspace}/{repo_slug}/environments": { 416 | "get": { 417 | "deployments": "listEnvironments", 418 | "repositories": "listEnvironments" 419 | }, 420 | "post": { 421 | "deployments": "createEnvironment", 422 | "repositories": "createEnvironment" 423 | } 424 | }, 425 | "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}": { 426 | "delete": { 427 | "deployments": "deleteEnvironmentForRepository", 428 | "repositories": "deleteEnvironment" 429 | }, 430 | "get": { 431 | "deployments": "getEnvironment", 432 | "repositories": "getEnvironment" 433 | } 434 | }, 435 | "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}/changes": { 436 | "post": { 437 | "deployments": "updateEnvironment", 438 | "repositories": "updateEnvironment" 439 | } 440 | }, 441 | "/repositories/{workspace}/{repo_slug}/filehistory/{commit}/{path}": { 442 | "get": { 443 | "repositories": "listFileHistory", 444 | "source": "listHistory" 445 | } 446 | }, 447 | "/repositories/{workspace}/{repo_slug}/forks": { 448 | "get": { 449 | "repositories": "listForks" 450 | }, 451 | "post": { 452 | "repositories": "createFork" 453 | } 454 | }, 455 | "/repositories/{workspace}/{repo_slug}/hooks": { 456 | "get": { 457 | "repositories": "listWebhooks", 458 | "webhooks": "listForRepo" 459 | }, 460 | "post": { 461 | "repositories": "createWebhook", 462 | "webhooks": "create" 463 | } 464 | }, 465 | "/repositories/{workspace}/{repo_slug}/hooks/{uid}": { 466 | "delete": { 467 | "repositories": "deleteWebhook", 468 | "webhooks": "delete" 469 | }, 470 | "get": { 471 | "repositories": "getWebhook", 472 | "webhooks": "get" 473 | }, 474 | "put": { 475 | "repositories": "updateWebhook", 476 | "webhooks": "update" 477 | } 478 | }, 479 | "/repositories/{workspace}/{repo_slug}/issues": { 480 | "get": { 481 | "issue_tracker": "list", 482 | "repositories": "listIssues" 483 | }, 484 | "post": { 485 | "issue_tracker": "create", 486 | "repositories": "createIssue" 487 | } 488 | }, 489 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}": { 490 | "delete": { 491 | "issue_tracker": "delete", 492 | "repositories": "deleteIssue" 493 | }, 494 | "get": { 495 | "issue_tracker": "get", 496 | "repositories": "getIssue" 497 | }, 498 | "put": { 499 | "issue_tracker": "update", 500 | "repositories": "updateIssue" 501 | } 502 | }, 503 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/attachments": { 504 | "get": { 505 | "issue_tracker": "listAttachments", 506 | "repositories": "listIssueAttachments" 507 | }, 508 | "post": { 509 | "issue_tracker": "createAttachments", 510 | "repositories": "createIssueAttachments" 511 | } 512 | }, 513 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/attachments/{path}": { 514 | "delete": { 515 | "issue_tracker": "deleteAttachment", 516 | "repositories": "deleteIssueAttachment" 517 | }, 518 | "get": { 519 | "issue_tracker": "getAttachment", 520 | "repositories": "getIssueAttachment" 521 | } 522 | }, 523 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/changes": { 524 | "get": { 525 | "issue_tracker": "listChanges", 526 | "repositories": "listIssueChanges" 527 | }, 528 | "post": { 529 | "issue_tracker": "createChange", 530 | "repositories": "createIssueChange" 531 | } 532 | }, 533 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/changes/{change_id}": { 534 | "get": { 535 | "issue_tracker": "getChange", 536 | "repositories": "getIssueChange" 537 | } 538 | }, 539 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/comments": { 540 | "get": { 541 | "issue_tracker": "listComments", 542 | "repositories": "listIssueComments" 543 | }, 544 | "post": { 545 | "issue_tracker": "createComment", 546 | "repositories": "createIssueComment" 547 | } 548 | }, 549 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/comments/{comment_id}": { 550 | "delete": { 551 | "issue_tracker": "deleteComment", 552 | "repositories": "deleteIssueComment" 553 | }, 554 | "get": { 555 | "issue_tracker": "getComment", 556 | "repositories": "getIssueComment" 557 | }, 558 | "put": { 559 | "issue_tracker": "updateComment", 560 | "repositories": "updateIssueComment" 561 | } 562 | }, 563 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/vote": { 564 | "delete": { 565 | "issue_tracker": "deleteVote", 566 | "repositories": "deleteIssueVote" 567 | }, 568 | "get": { 569 | "issue_tracker": "getVote", 570 | "repositories": "getIssueVote" 571 | }, 572 | "put": { 573 | "issue_tracker": "createVote", 574 | "repositories": "createIssueVote" 575 | } 576 | }, 577 | "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/watch": { 578 | "delete": { 579 | "issue_tracker": "deleteWatch", 580 | "repositories": "deleteIssueWatch" 581 | }, 582 | "get": { 583 | "issue_tracker": "getWatch", 584 | "repositories": "getIssueWatch" 585 | }, 586 | "put": { 587 | "issue_tracker": "createWatch", 588 | "repositories": "createIssueWatch" 589 | } 590 | }, 591 | "/repositories/{workspace}/{repo_slug}/issues/export": { 592 | "post": { 593 | "issue_tracker": "", 594 | "repositories": "createIssueExportJob" 595 | } 596 | }, 597 | "/repositories/{workspace}/{repo_slug}/issues/export/{repo_name}-issues-{task_id}.zip": { 598 | "get": { 599 | "issue_tracker": "", 600 | "repositories": "getIssueExportJobStatus" 601 | } 602 | }, 603 | "/repositories/{workspace}/{repo_slug}/issues/import": { 604 | "get": { 605 | "issue_tracker": "", 606 | "repositories": "getIssueImportJobStatus" 607 | }, 608 | "post": { 609 | "issue_tracker": "", 610 | "repositories": "createIssueImportJob" 611 | } 612 | }, 613 | "/repositories/{workspace}/{repo_slug}/merge-base/{revspec}": { 614 | "get": { 615 | "commits": "", 616 | "repositories": "getMergeBase" 617 | } 618 | }, 619 | "/repositories/{workspace}/{repo_slug}/milestones": { 620 | "get": { 621 | "issue_tracker": "listMilestones", 622 | "repositories": "listMilestones" 623 | } 624 | }, 625 | "/repositories/{workspace}/{repo_slug}/milestones/{milestone_id}": { 626 | "get": { 627 | "issue_tracker": "getMilestone", 628 | "repositories": "getIssueMilestone" 629 | } 630 | }, 631 | "/repositories/{workspace}/{repo_slug}/override-settings": { 632 | "get": { 633 | "repositories": "" 634 | }, 635 | "put": { 636 | "repositories": "" 637 | } 638 | }, 639 | "/repositories/{workspace}/{repo_slug}/patch/{spec}": { 640 | "get": { 641 | "commits": "getPatch", 642 | "repositories": "getPatch" 643 | } 644 | }, 645 | "/repositories/{workspace}/{repo_slug}/permissions-config/groups": { 646 | "get": { 647 | "repositories": "listGroupPermissions" 648 | } 649 | }, 650 | "/repositories/{workspace}/{repo_slug}/permissions-config/groups/{group_slug}": { 651 | "delete": { 652 | "repositories": "deleteGroupPermission" 653 | }, 654 | "get": { 655 | "repositories": "getGroupPermission" 656 | }, 657 | "put": { 658 | "repositories": "updateGroupPermission" 659 | } 660 | }, 661 | "/repositories/{workspace}/{repo_slug}/permissions-config/users": { 662 | "get": { 663 | "repositories": "listUserPermissions" 664 | } 665 | }, 666 | "/repositories/{workspace}/{repo_slug}/permissions-config/users/{selected_user_id}": { 667 | "delete": { 668 | "repositories": "deleteUserPermission" 669 | }, 670 | "get": { 671 | "repositories": "getUserPermission" 672 | }, 673 | "put": { 674 | "repositories": "updateUserPermission" 675 | } 676 | }, 677 | "/repositories/{workspace}/{repo_slug}/pipelines": { 678 | "get": { 679 | "pipelines": "list", 680 | "repositories": "listPipelines" 681 | }, 682 | "post": { 683 | "pipelines": "create", 684 | "repositories": "createPipeline" 685 | } 686 | }, 687 | "/repositories/{workspace}/{repo_slug}/pipelines_config": { 688 | "get": { 689 | "pipelines": "getConfig", 690 | "repositories": "getPipelineConfig" 691 | }, 692 | "put": { 693 | "pipelines": "updateConfig", 694 | "repositories": "updatePipelineConfig" 695 | } 696 | }, 697 | "/repositories/{workspace}/{repo_slug}/pipelines_config/build_number": { 698 | "put": { 699 | "pipelines": "updateBuildNumber", 700 | "repositories": "updatePipelineBuildNumber" 701 | } 702 | }, 703 | "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules": { 704 | "get": { 705 | "pipelines": "listSchedules", 706 | "repositories": "listPipelineSchedules" 707 | }, 708 | "post": { 709 | "pipelines": "createSchedule", 710 | "repositories": "createPipelineSchedule" 711 | } 712 | }, 713 | "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}": { 714 | "delete": { 715 | "pipelines": "deleteSchedule", 716 | "repositories": "deletePipelineSchedule" 717 | }, 718 | "get": { 719 | "pipelines": "getSchedule", 720 | "repositories": "getPipelineSchedule" 721 | }, 722 | "put": { 723 | "pipelines": "updateSchedule", 724 | "repositories": "updatePipelineSchedule" 725 | } 726 | }, 727 | "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}/executions": { 728 | "get": { 729 | "pipelines": "listScheduleExecutions", 730 | "repositories": "listPipelineScheduleExecutions" 731 | } 732 | }, 733 | "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/key_pair": { 734 | "delete": { 735 | "pipelines": "deleteSshKeyPair", 736 | "repositories": "deletePipelineSshKeyPair" 737 | }, 738 | "get": { 739 | "pipelines": "getSshKeyPair", 740 | "repositories": "getPipelineSshKeyPair" 741 | }, 742 | "put": { 743 | "pipelines": "updateSshKeyPair", 744 | "repositories": "updatePipelineSshKeyPair" 745 | } 746 | }, 747 | "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts": { 748 | "get": { 749 | "pipelines": "listKnownHosts", 750 | "repositories": "listPipelineKnownHosts" 751 | }, 752 | "post": { 753 | "pipelines": "createKnownHost", 754 | "repositories": "createPipelineKnownHost" 755 | } 756 | }, 757 | "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/{known_host_uuid}": { 758 | "delete": { 759 | "pipelines": "deleteKnownHost", 760 | "repositories": "deletePipelineKnownHost" 761 | }, 762 | "get": { 763 | "pipelines": "getKnownHost", 764 | "repositories": "getPipelineKnownHost" 765 | }, 766 | "put": { 767 | "pipelines": "updateKnownHost", 768 | "repositories": "updatePipelineKnownHost" 769 | } 770 | }, 771 | "/repositories/{workspace}/{repo_slug}/pipelines_config/variables": { 772 | "get": { 773 | "pipelines": "listVariablesForRepo", 774 | "repositories": "listPipelineVariables" 775 | }, 776 | "post": { 777 | "pipelines": "createVariable", 778 | "repositories": "createPipelineVariable" 779 | } 780 | }, 781 | "/repositories/{workspace}/{repo_slug}/pipelines_config/variables/{variable_uuid}": { 782 | "delete": { 783 | "pipelines": "deleteVariable", 784 | "repositories": "deletePipelineVariable" 785 | }, 786 | "get": { 787 | "pipelines": "getVariable", 788 | "repositories": "getPipelineVariable" 789 | }, 790 | "put": { 791 | "pipelines": "updateVariable", 792 | "repositories": "updatePipelineVariable" 793 | } 794 | }, 795 | "/repositories/{workspace}/{repo_slug}/pipelines-config/caches": { 796 | "delete": { 797 | "pipelines": "deleteRepositoryPipelineCaches", 798 | "repositories": "deleteRepositoryPipelineCaches" 799 | }, 800 | "get": { 801 | "pipelines": "getRepositoryPipelineCaches", 802 | "repositories": "getRepositoryPipelineCaches" 803 | } 804 | }, 805 | "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}": { 806 | "delete": { 807 | "pipelines": "deleteRepositoryPipelineCache", 808 | "repositories": "deleteRepositoryPipelineCache" 809 | } 810 | }, 811 | "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}/content-uri": { 812 | "get": { 813 | "pipelines": "getRepositoryPipelineCacheContentURI", 814 | "repositories": "getRepositoryPipelineCacheContentURI" 815 | } 816 | }, 817 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}": { 818 | "get": { 819 | "pipelines": "get", 820 | "repositories": "getPipeline" 821 | } 822 | }, 823 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps": { 824 | "get": { 825 | "pipelines": "listSteps", 826 | "repositories": "listPipelineSteps" 827 | } 828 | }, 829 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}": { 830 | "get": { 831 | "pipelines": "getStep", 832 | "repositories": "getPipelineStep" 833 | } 834 | }, 835 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/log": { 836 | "get": { 837 | "pipelines": "getStepLog", 838 | "repositories": "getPipelineStepLog" 839 | } 840 | }, 841 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/logs/{log_uuid}": { 842 | "get": { 843 | "pipelines": "getPipelineContainerLog", 844 | "repositories": "getPipelineContainerLog" 845 | } 846 | }, 847 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports": { 848 | "get": { 849 | "pipelines": "getPipelineTestReports", 850 | "repositories": "getPipelineTestReports" 851 | } 852 | }, 853 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases": { 854 | "get": { 855 | "pipelines": "getPipelineTestReportTestCases", 856 | "repositories": "getPipelineTestReportTestCases" 857 | } 858 | }, 859 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases/{test_case_uuid}/test_case_reasons": { 860 | "get": { 861 | "pipelines": "getPipelineTestReportTestCaseReasons", 862 | "repositories": "getPipelineTestReportTestCaseReasons" 863 | } 864 | }, 865 | "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/stopPipeline": { 866 | "post": { 867 | "pipelines": "stop", 868 | "repositories": "stopPipeline" 869 | } 870 | }, 871 | "/repositories/{workspace}/{repo_slug}/properties/{app_key}/{property_name}": { 872 | "delete": { 873 | "properties": "deleteRepositoryHostedPropertyValue", 874 | "repositories": "deleteRepositoryHostedPropertyValue" 875 | }, 876 | "get": { 877 | "properties": "getRepositoryHostedPropertyValue", 878 | "repositories": "getRepositoryHostedPropertyValue" 879 | }, 880 | "put": { 881 | "properties": "updateRepositoryHostedPropertyValue", 882 | "repositories": "updateRepositoryHostedPropertyValue" 883 | } 884 | }, 885 | "/repositories/{workspace}/{repo_slug}/pullrequests": { 886 | "get": { 887 | "pullrequests": "list", 888 | "repositories": "listPullRequests" 889 | }, 890 | "post": { 891 | "pullrequests": "create", 892 | "repositories": "createPullRequest" 893 | } 894 | }, 895 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}": { 896 | "get": { 897 | "pullrequests": "get", 898 | "repositories": "getPullRequest" 899 | }, 900 | "put": { 901 | "pullrequests": "update", 902 | "repositories": "updatePullRequest" 903 | } 904 | }, 905 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/activity": { 906 | "get": { 907 | "pullrequests": "listActivities", 908 | "repositories": "listPullRequestActivities" 909 | } 910 | }, 911 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/approve": { 912 | "delete": { 913 | "pullrequests": "deleteApproval", 914 | "repositories": "deletePullRequestApproval" 915 | }, 916 | "post": { 917 | "pullrequests": "createApproval", 918 | "repositories": "createPullRequestApproval" 919 | } 920 | }, 921 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments": { 922 | "get": { 923 | "pullrequests": "listComments", 924 | "repositories": "listPullRequestComments" 925 | }, 926 | "post": { 927 | "pullrequests": "createComment", 928 | "repositories": "createPullRequestComment" 929 | } 930 | }, 931 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments/{comment_id}": { 932 | "delete": { 933 | "pullrequests": "deleteComment", 934 | "repositories": "deletePullRequestComment" 935 | }, 936 | "get": { 937 | "pullrequests": "getComment", 938 | "repositories": "getPullRequestComment" 939 | }, 940 | "put": { 941 | "pullrequests": "updateComment", 942 | "repositories": "updatePullRequestComment" 943 | } 944 | }, 945 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments/{comment_id}/resolve": { 946 | "delete": { 947 | "pullrequests": "reopenComment", 948 | "repositories": "reopenPullRequestComment" 949 | }, 950 | "post": { 951 | "pullrequests": "resolveComment", 952 | "repositories": "resolvePullRequestComment" 953 | } 954 | }, 955 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/commits": { 956 | "get": { 957 | "pullrequests": "listCommits", 958 | "repositories": "listPullRequestCommits" 959 | } 960 | }, 961 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/decline": { 962 | "post": { 963 | "pullrequests": "decline", 964 | "repositories": "declinePullRequest" 965 | } 966 | }, 967 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diff": { 968 | "get": { 969 | "pullrequests": "getDiff", 970 | "repositories": "getPullRequestDiff" 971 | } 972 | }, 973 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diffstat": { 974 | "get": { 975 | "pullrequests": "getDiffStat", 976 | "repositories": "getPullRequestDiffStat" 977 | } 978 | }, 979 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/merge": { 980 | "post": { 981 | "pullrequests": "merge", 982 | "repositories": "mergePullRequest" 983 | } 984 | }, 985 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/merge/task-status/{task_id}": { 986 | "get": { 987 | "pullrequests": "getMergeTaskStatus", 988 | "repositories": "getMergeTaskStatusForPullRequest" 989 | } 990 | }, 991 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/patch": { 992 | "get": { 993 | "pullrequests": "getPatch", 994 | "repositories": "getPullRequestPatch" 995 | } 996 | }, 997 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/request-changes": { 998 | "delete": { 999 | "pullrequests": "deleteChangeRequest", 1000 | "repositories": "deleteChangeRequestForPullRequest" 1001 | }, 1002 | "post": { 1003 | "pullrequests": "addChangeRequest", 1004 | "repositories": "addChangeRequestForPullRequest" 1005 | } 1006 | }, 1007 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/statuses": { 1008 | "get": { 1009 | "commitstatuses": "listPullRequestStatuses", 1010 | "pullrequests": "listStatuses", 1011 | "repositories": "listPullRequestStatuses" 1012 | } 1013 | }, 1014 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/tasks": { 1015 | "get": { 1016 | "pullrequests": "listTasks", 1017 | "repositories": "listPullRequestTasks" 1018 | }, 1019 | "post": { 1020 | "pullrequests": "createTask", 1021 | "repositories": "createPullRequestTask" 1022 | } 1023 | }, 1024 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/tasks/{task_id}": { 1025 | "delete": { 1026 | "pullrequests": "deleteTask", 1027 | "repositories": "deletePullRequestTask" 1028 | }, 1029 | "get": { 1030 | "pullrequests": "getTask", 1031 | "repositories": "getPullRequestTask" 1032 | }, 1033 | "put": { 1034 | "pullrequests": "updateTask", 1035 | "repositories": "updatePullRequestTask" 1036 | } 1037 | }, 1038 | "/repositories/{workspace}/{repo_slug}/pullrequests/{pullrequest_id}/properties/{app_key}/{property_name}": { 1039 | "delete": { 1040 | "properties": "deletePullRequestHostedPropertyValue", 1041 | "repositories": "deletePullRequestHostedPropertyValue" 1042 | }, 1043 | "get": { 1044 | "properties": "getPullRequestHostedPropertyValue", 1045 | "repositories": "getPullRequestHostedPropertyValue" 1046 | }, 1047 | "put": { 1048 | "properties": "updatePullRequestHostedPropertyValue", 1049 | "repositories": "updatePullRequestHostedPropertyValue" 1050 | } 1051 | }, 1052 | "/repositories/{workspace}/{repo_slug}/pullrequests/activity": { 1053 | "get": { 1054 | "pullrequests": "listActivitiesForRepo", 1055 | "repositories": "listPullRequestActivitiesForRepo" 1056 | } 1057 | }, 1058 | "/repositories/{workspace}/{repo_slug}/refs": { 1059 | "get": { 1060 | "refs": "list", 1061 | "repositories": "listRefs" 1062 | } 1063 | }, 1064 | "/repositories/{workspace}/{repo_slug}/refs/branches": { 1065 | "get": { 1066 | "refs": "listBranches", 1067 | "repositories": "listBranches" 1068 | }, 1069 | "post": { 1070 | "refs": "createBranch", 1071 | "repositories": "createBranch" 1072 | } 1073 | }, 1074 | "/repositories/{workspace}/{repo_slug}/refs/branches/{name}": { 1075 | "delete": { 1076 | "refs": "deleteBranch", 1077 | "repositories": "deleteBranch" 1078 | }, 1079 | "get": { 1080 | "refs": "getBranch", 1081 | "repositories": "getBranch" 1082 | } 1083 | }, 1084 | "/repositories/{workspace}/{repo_slug}/refs/tags": { 1085 | "get": { 1086 | "refs": "listTags", 1087 | "repositories": "listTags" 1088 | }, 1089 | "post": { 1090 | "refs": "createTag", 1091 | "repositories": "createTag" 1092 | } 1093 | }, 1094 | "/repositories/{workspace}/{repo_slug}/refs/tags/{name}": { 1095 | "delete": { 1096 | "refs": "deleteTag", 1097 | "repositories": "deleteTag" 1098 | }, 1099 | "get": { 1100 | "refs": "getTag", 1101 | "repositories": "getTag" 1102 | } 1103 | }, 1104 | "/repositories/{workspace}/{repo_slug}/src": { 1105 | "get": { 1106 | "repositories": "readSrcRoot", 1107 | "source": "readRoot" 1108 | }, 1109 | "post": { 1110 | "repositories": "createSrcFileCommit", 1111 | "source": "createFileCommit" 1112 | } 1113 | }, 1114 | "/repositories/{workspace}/{repo_slug}/src/{commit}/{path}": { 1115 | "get": { 1116 | "repositories": "readSrc", 1117 | "source": "read" 1118 | } 1119 | }, 1120 | "/repositories/{workspace}/{repo_slug}/versions": { 1121 | "get": { 1122 | "issue_tracker": "listVersions", 1123 | "repositories": "listVersions" 1124 | } 1125 | }, 1126 | "/repositories/{workspace}/{repo_slug}/versions/{version_id}": { 1127 | "get": { 1128 | "issue_tracker": "getVersion", 1129 | "repositories": "getIssueVersion" 1130 | } 1131 | }, 1132 | "/repositories/{workspace}/{repo_slug}/watchers": { 1133 | "get": { 1134 | "repositories": "listWatchers" 1135 | } 1136 | }, 1137 | "/snippets": { 1138 | "get": { 1139 | "snippets": "list" 1140 | }, 1141 | "post": { 1142 | "snippets": "create" 1143 | } 1144 | }, 1145 | "/snippets/{workspace}": { 1146 | "get": { 1147 | "snippets": "listForUser" 1148 | }, 1149 | "post": { 1150 | "snippets": "createForUser" 1151 | } 1152 | }, 1153 | "/snippets/{workspace}/{encoded_id}": { 1154 | "delete": { 1155 | "snippets": "delete" 1156 | }, 1157 | "get": { 1158 | "snippets": "get" 1159 | }, 1160 | "put": { 1161 | "snippets": "update" 1162 | } 1163 | }, 1164 | "/snippets/{workspace}/{encoded_id}/{node_id}": { 1165 | "delete": { 1166 | "snippets": "deleteAt" 1167 | }, 1168 | "get": { 1169 | "snippets": "getAt" 1170 | }, 1171 | "put": { 1172 | "snippets": "updateAt" 1173 | } 1174 | }, 1175 | "/snippets/{workspace}/{encoded_id}/{node_id}/files/{path}": { 1176 | "get": { 1177 | "snippets": "getFile" 1178 | } 1179 | }, 1180 | "/snippets/{workspace}/{encoded_id}/{revision}/diff": { 1181 | "get": { 1182 | "snippets": "getDiff" 1183 | } 1184 | }, 1185 | "/snippets/{workspace}/{encoded_id}/{revision}/patch": { 1186 | "get": { 1187 | "snippets": "getPatch" 1188 | } 1189 | }, 1190 | "/snippets/{workspace}/{encoded_id}/comments": { 1191 | "get": { 1192 | "snippets": "listComments" 1193 | }, 1194 | "post": { 1195 | "snippets": "createComment" 1196 | } 1197 | }, 1198 | "/snippets/{workspace}/{encoded_id}/comments/{comment_id}": { 1199 | "delete": { 1200 | "snippets": "deleteComment" 1201 | }, 1202 | "get": { 1203 | "snippets": "getComment" 1204 | }, 1205 | "put": { 1206 | "snippets": "updateComment" 1207 | } 1208 | }, 1209 | "/snippets/{workspace}/{encoded_id}/commits": { 1210 | "get": { 1211 | "snippets": "listCommits" 1212 | } 1213 | }, 1214 | "/snippets/{workspace}/{encoded_id}/commits/{revision}": { 1215 | "get": { 1216 | "snippets": "getCommit" 1217 | } 1218 | }, 1219 | "/snippets/{workspace}/{encoded_id}/files/{path}": { 1220 | "get": { 1221 | "snippet": "getRawFiles", 1222 | "snippets": "getRawFiles" 1223 | } 1224 | }, 1225 | "/snippets/{workspace}/{encoded_id}/watch": { 1226 | "delete": { 1227 | "snippets": "stopWatch" 1228 | }, 1229 | "get": { 1230 | "snippets": "checkWatch" 1231 | }, 1232 | "put": { 1233 | "snippets": "startWatch" 1234 | } 1235 | }, 1236 | "/snippets/{workspace}/{encoded_id}/watchers": { 1237 | "get": { 1238 | "snippets": "listWatchers" 1239 | } 1240 | }, 1241 | "/teams/{username}/pipelines_config/variables": { 1242 | "get": { 1243 | "pipelines": "listVariablesForTeam", 1244 | "teams": "listPipelineVariables" 1245 | }, 1246 | "post": { 1247 | "pipelines": "createVariableForTeam", 1248 | "teams": "createPipelineVariable" 1249 | } 1250 | }, 1251 | "/teams/{username}/pipelines_config/variables/{variable_uuid}": { 1252 | "delete": { 1253 | "pipelines": "deleteVariableForTeam", 1254 | "teams": "deletePipelineVariable" 1255 | }, 1256 | "get": { 1257 | "pipelines": "getVariableForTeam", 1258 | "teams": "getPipelineVariable" 1259 | }, 1260 | "put": { 1261 | "pipelines": "updateVariableForTeam", 1262 | "teams": "updatePipelineVariable" 1263 | } 1264 | }, 1265 | "/teams/{username}/search/code": { 1266 | "get": { 1267 | "search": "codeOfTeam", 1268 | "teams": "searchCode" 1269 | } 1270 | }, 1271 | "/user": { 1272 | "get": { 1273 | "user": "get", 1274 | "users": "getAuthedUser" 1275 | } 1276 | }, 1277 | "/user/emails": { 1278 | "get": { 1279 | "user": "listEmails", 1280 | "users": "listEmailsForAuthedUser" 1281 | } 1282 | }, 1283 | "/user/emails/{email}": { 1284 | "get": { 1285 | "user": "getEmail", 1286 | "users": "getEmailForAuthedUser" 1287 | } 1288 | }, 1289 | "/user/permissions/repositories": { 1290 | "get": { 1291 | "repositories": "listPermissions", 1292 | "user": "listPermissionsForRepos" 1293 | } 1294 | }, 1295 | "/user/permissions/workspaces": { 1296 | "get": { 1297 | "user": "listPermissionsForWorkspaces", 1298 | "workspaces": "" 1299 | } 1300 | }, 1301 | "/users/{selected_user}": { 1302 | "get": { 1303 | "users": "get" 1304 | } 1305 | }, 1306 | "/users/{selected_user}/pipelines_config/variables": { 1307 | "get": { 1308 | "pipelines": "listVariablesForUser", 1309 | "users": "listPipelineVariables" 1310 | }, 1311 | "post": { 1312 | "pipelines": "createVariableForUser", 1313 | "users": "createPipelineVariable" 1314 | } 1315 | }, 1316 | "/users/{selected_user}/pipelines_config/variables/{variable_uuid}": { 1317 | "delete": { 1318 | "pipelines": "deleteVariableForUser", 1319 | "users": "deletePipelineVariable" 1320 | }, 1321 | "get": { 1322 | "pipelines": "getVariableForUser", 1323 | "users": "getPipelineVariable" 1324 | }, 1325 | "put": { 1326 | "pipelines": "updateVariableForUser", 1327 | "users": "updatePipelineVariable" 1328 | } 1329 | }, 1330 | "/users/{selected_user}/properties/{app_key}/{property_name}": { 1331 | "delete": { 1332 | "properties": "deleteUserHostedPropertyValue", 1333 | "users": "deleteUserHostedPropertyValue" 1334 | }, 1335 | "get": { 1336 | "properties": "retrieveUserHostedPropertyValue", 1337 | "users": "retrieveUserHostedPropertyValue" 1338 | }, 1339 | "put": { 1340 | "properties": "updateUserHostedPropertyValue", 1341 | "users": "updateUserHostedPropertyValue" 1342 | } 1343 | }, 1344 | "/users/{selected_user}/search/code": { 1345 | "get": { 1346 | "search": "codeOfUser", 1347 | "users": "searchCode" 1348 | } 1349 | }, 1350 | "/users/{selected_user}/ssh-keys": { 1351 | "get": { 1352 | "ssh": "listKeys", 1353 | "users": "listSshKeys" 1354 | }, 1355 | "post": { 1356 | "ssh": "createKey", 1357 | "users": "createSshKey" 1358 | } 1359 | }, 1360 | "/users/{selected_user}/ssh-keys/{key_id}": { 1361 | "delete": { 1362 | "ssh": "deleteKey", 1363 | "users": "deleteSshKey" 1364 | }, 1365 | "get": { 1366 | "ssh": "getKey", 1367 | "users": "getSshKey" 1368 | }, 1369 | "put": { 1370 | "ssh": "updateKey", 1371 | "users": "updateSshKey" 1372 | } 1373 | }, 1374 | "/workspaces": { 1375 | "get": { 1376 | "workspaces": "getWorkspaces" 1377 | } 1378 | }, 1379 | "/workspaces/{workspace}": { 1380 | "get": { 1381 | "workspaces": "getWorkspace" 1382 | } 1383 | }, 1384 | "/workspaces/{workspace}/hooks": { 1385 | "get": { 1386 | "webhooks": "getWebhooksForWorkspace", 1387 | "workspaces": "getWebhooksForWorkspace" 1388 | }, 1389 | "post": { 1390 | "webhooks": "createWebhookForWorkspace", 1391 | "workspaces": "createWebhookForWorkspace" 1392 | } 1393 | }, 1394 | "/workspaces/{workspace}/hooks/{uid}": { 1395 | "delete": { 1396 | "webhooks": "deleteWebhookForWorkspace", 1397 | "workspaces": "deleteWebhookForWorkspace" 1398 | }, 1399 | "get": { 1400 | "webhooks": "getWebhookForWorkspace", 1401 | "workspaces": "getWebhookForWorkspace" 1402 | }, 1403 | "put": { 1404 | "webhooks": "updateWebhookForWorkspace", 1405 | "workspaces": "updateWebhookForWorkspace" 1406 | } 1407 | }, 1408 | "/workspaces/{workspace}/members": { 1409 | "get": { 1410 | "workspaces": "getMembersForWorkspace" 1411 | } 1412 | }, 1413 | "/workspaces/{workspace}/members/{member}": { 1414 | "get": { 1415 | "workspaces": "getMemberForWorkspace" 1416 | } 1417 | }, 1418 | "/workspaces/{workspace}/permissions": { 1419 | "get": { 1420 | "workspaces": "listPermissions" 1421 | } 1422 | }, 1423 | "/workspaces/{workspace}/permissions/repositories": { 1424 | "get": { 1425 | "workspaces": "listPermissionsForRepositories" 1426 | } 1427 | }, 1428 | "/workspaces/{workspace}/permissions/repositories/{repo_slug}": { 1429 | "get": { 1430 | "workspaces": "listPermissionsForRepository" 1431 | } 1432 | }, 1433 | "/workspaces/{workspace}/pipelines-config/identity/oidc/.well-known/openid-configuration": { 1434 | "get": { 1435 | "pipelines": "getOIDCConfiguration", 1436 | "workspaces": "getOIDCConfiguration" 1437 | } 1438 | }, 1439 | "/workspaces/{workspace}/pipelines-config/identity/oidc/keys.json": { 1440 | "get": { 1441 | "pipelines": "getOIDCKeys", 1442 | "workspaces": "getOIDCKeys" 1443 | } 1444 | }, 1445 | "/workspaces/{workspace}/pipelines-config/variables": { 1446 | "get": { 1447 | "pipelines": "getPipelineVariablesForWorkspace", 1448 | "workspaces": "getPipelineVariablesForWorkspace" 1449 | }, 1450 | "post": { 1451 | "pipelines": "createPipelineVariableForWorkspace", 1452 | "workspaces": "createPipelineVariableForWorkspace" 1453 | } 1454 | }, 1455 | "/workspaces/{workspace}/pipelines-config/variables/{variable_uuid}": { 1456 | "delete": { 1457 | "pipelines": "deletePipelineVariableForWorkspace", 1458 | "workspaces": "deletePipelineVariableForWorkspace" 1459 | }, 1460 | "get": { 1461 | "pipelines": "getPipelineVariableForWorkspace", 1462 | "workspaces": "getPipelineVariableForWorkspace" 1463 | }, 1464 | "put": { 1465 | "pipelines": "updatePipelineVariableForWorkspace", 1466 | "workspaces": "updatePipelineVariableForWorkspace" 1467 | } 1468 | }, 1469 | "/workspaces/{workspace}/projects": { 1470 | "get": { 1471 | "workspaces": "getProjects" 1472 | }, 1473 | "post": { 1474 | "projects": "createProject", 1475 | "workspaces": "createProject" 1476 | } 1477 | }, 1478 | "/workspaces/{workspace}/projects/{project_key}": { 1479 | "delete": { 1480 | "projects": "deleteProject", 1481 | "workspaces": "deleteProject" 1482 | }, 1483 | "get": { 1484 | "projects": "getProject", 1485 | "workspaces": "getProject" 1486 | }, 1487 | "put": { 1488 | "projects": "createOrUpdateProject", 1489 | "workspaces": "createOrUpdateProject" 1490 | } 1491 | }, 1492 | "/workspaces/{workspace}/projects/{project_key}/branching-model": { 1493 | "get": { 1494 | "branching_model": "getForProject", 1495 | "workspaces": "getBranchingModelForProject" 1496 | } 1497 | }, 1498 | "/workspaces/{workspace}/projects/{project_key}/branching-model/settings": { 1499 | "get": { 1500 | "branching_model": "getSettingsForProject", 1501 | "workspaces": "getBranchingModelSettingsForProject" 1502 | }, 1503 | "put": { 1504 | "branching_model": "updateSettingsForProject", 1505 | "workspaces": "updateBranchingModelSettingsForProject" 1506 | } 1507 | }, 1508 | "/workspaces/{workspace}/projects/{project_key}/default-reviewers": { 1509 | "get": { 1510 | "projects": "listDefaultReviewers", 1511 | "workspaces": "listDefaultReviewersForProject" 1512 | } 1513 | }, 1514 | "/workspaces/{workspace}/projects/{project_key}/default-reviewers/{selected_user}": { 1515 | "delete": { 1516 | "projects": "deleteDefaultReviewer", 1517 | "workspaces": "deleteDefaultReviewerForProject" 1518 | }, 1519 | "get": { 1520 | "projects": "getDefaultReviewer", 1521 | "workspaces": "getDefaultReviewerForProject" 1522 | }, 1523 | "put": { 1524 | "projects": "addDefaultReviewer", 1525 | "workspaces": "addDefaultReviewerForProject" 1526 | } 1527 | }, 1528 | "/workspaces/{workspace}/projects/{project_key}/deploy-keys": { 1529 | "get": { 1530 | "deployments": "listProjectDeployKeys", 1531 | "workspaces": "listProjectDeployKeys" 1532 | }, 1533 | "post": { 1534 | "deployments": "createProjectDeployKey", 1535 | "workspaces": "createProjectDeployKey" 1536 | } 1537 | }, 1538 | "/workspaces/{workspace}/projects/{project_key}/deploy-keys/{key_id}": { 1539 | "delete": { 1540 | "deployments": "deleteProjectDeployKey", 1541 | "workspaces": "deleteProjectDeployKey" 1542 | }, 1543 | "get": { 1544 | "deployments": "getProjectDeployKey", 1545 | "workspaces": "getProjectDeployKey" 1546 | } 1547 | }, 1548 | "/workspaces/{workspace}/projects/{project_key}/permissions-config/groups": { 1549 | "get": { 1550 | "projects": "listGroupPermissions", 1551 | "workspaces": "listProjectGroupPermissions" 1552 | } 1553 | }, 1554 | "/workspaces/{workspace}/projects/{project_key}/permissions-config/groups/{group_slug}": { 1555 | "delete": { 1556 | "projects": "deleteGroupPermission", 1557 | "workspaces": "deleteProjectGroupPermission" 1558 | }, 1559 | "get": { 1560 | "projects": "getGroupPermission", 1561 | "workspaces": "getProjectGroupPermission" 1562 | }, 1563 | "put": { 1564 | "projects": "updateGroupPermission", 1565 | "workspaces": "updateProjectGroupPermission" 1566 | } 1567 | }, 1568 | "/workspaces/{workspace}/projects/{project_key}/permissions-config/users": { 1569 | "get": { 1570 | "projects": "listUserPermissions", 1571 | "workspaces": "listProjectUserPermissions" 1572 | } 1573 | }, 1574 | "/workspaces/{workspace}/projects/{project_key}/permissions-config/users/{selected_user_id}": { 1575 | "delete": { 1576 | "projects": "deleteUserPermission", 1577 | "workspaces": "deleteProjectUserPermission" 1578 | }, 1579 | "get": { 1580 | "projects": "getUserPermission", 1581 | "workspaces": "getProjectUserPermission" 1582 | }, 1583 | "put": { 1584 | "projects": "updateUserPermission", 1585 | "workspaces": "updateProjectUserPermission" 1586 | } 1587 | }, 1588 | "/workspaces/{workspace}/search/code": { 1589 | "get": { 1590 | "search": "searchAccount", 1591 | "workspaces": "searchAccount" 1592 | } 1593 | } 1594 | } 1595 | --------------------------------------------------------------------------------