diff --git a/packages/gitbeaker-browser/package.json b/packages/gitbeaker-browser/package.json new file mode 100644 index 00000000..7b90078c --- /dev/null +++ b/packages/gitbeaker-browser/package.json @@ -0,0 +1,54 @@ +{ + "name": "@gitbeaker/browser", + "description": "Full Browser implementation of the GitLab API. Supports Promises, Async/Await.", + "version": "15.0.0", + "author": { + "name": "Justin Dalrymple" + }, + "bugs": { + "url": "https://github.com/jdalrymple/gitbeaker/issues" + }, + "dependencies": { + "@gitbeaker/core": "^15.0.0", + "@gitbeaker/requester-utils": "^15.0.0", + "ky": "^0.16.2" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^11.0.1", + "@rollup/plugin-node-resolve": "^7.0.0", + "@rollup/plugin-replace": "^2.3.0", + "@types/node": "^13.7.0", + "node-fetch": "^2.6.0", + "rollup": "^1.31.0", + "rollup-plugin-node-builtins": "^2.1.2", + "rollup-plugin-node-globals": "^1.4.0", + "rollup-plugin-terser": "^5.2.0", + "rollup-plugin-typescript2": "^0.25.3", + "ts-node": "^8.6.2", + "typescript": "^3.7.5" + }, + "engines": { + "node": ">=10.0.0" + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/jdalrymple/gitbeaker#readme", + "keywords": [ + "api", + "es5", + "es6", + "gitlab", + "gitbeaker", + "ky" + ], + "license": "MIT", + "browser": "dist/index.js", + "repository": { + "type": "git", + "url": "https://github.com/jdalrymple/gitbeaker" + }, + "scripts": { + "build": "tsc && rollup -c" + } +} diff --git a/packages/gitbeaker-browser/rollup.config.js b/packages/gitbeaker-browser/rollup.config.js new file mode 100644 index 00000000..a539f691 --- /dev/null +++ b/packages/gitbeaker-browser/rollup.config.js @@ -0,0 +1,19 @@ +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import builtins from 'rollup-plugin-node-builtins'; +import globals from 'rollup-plugin-node-globals'; +import pkg from './package.json'; +import { commonConfig, commonPlugins } from '../../rollup.config'; + +export default [ + { + ...commonConfig, + output: { + file: pkg.browser, + name: 'gitbeaker', + format: 'umd', + exports: 'named', + }, + plugins: [globals(), builtins(), resolve({ browser: true }), commonjs(), ...commonPlugins], + }, +]; diff --git a/packages/gitbeaker-browser/src/KyRequester.ts b/packages/gitbeaker-browser/src/KyRequester.ts new file mode 100644 index 00000000..a02f8427 --- /dev/null +++ b/packages/gitbeaker-browser/src/KyRequester.ts @@ -0,0 +1,66 @@ +import Ky from 'ky'; +import { + Service, + DefaultRequestOptions, + createInstance, + defaultRequest as baseDefaultRequest, +} from '@gitbeaker/requester-utils'; + +function responseHeadersAsObject(response): Record { + const headers = {}; + const keyVals = [...response.headers.entries()]; + + keyVals.forEach(([key, val]) => { + headers[key] = val; + }); + + return headers; +} + +export function defaultRequest(service: Service, options: DefaultRequestOptions = {}) { + const opts = baseDefaultRequest(service, options); + + return { ...opts, headers: new Headers(service.headers as Record) }; +} + +export function processBody(response) { + const contentType = response.headers.get('content-type') || ''; + + switch (contentType) { + case 'application/json': { + return response.json().then(v => v || {}); + } + case 'application/octet-stream': + case 'binary/octet-stream': + case 'application/gzip': { + return response.blob().then(Buffer.from); + } + default: { + return response.text().then(t => t || ''); + } + } +} + +export async function handler(endpoint, options) { + let response; + + try { + response = await Ky(endpoint, options); + } catch (e) { + if (e.response) { + const output = await e.response.json(); + + e.description = output.error || output.message; + } + + throw e; + } + + const { status } = response; + const headers = responseHeadersAsObject(response); + const body = await processBody(response); + + return { body, headers, status }; +} + +export const Requester = createInstance(defaultRequest, handler); diff --git a/packages/gitbeaker-browser/src/index.ts b/packages/gitbeaker-browser/src/index.ts new file mode 100644 index 00000000..f2757556 --- /dev/null +++ b/packages/gitbeaker-browser/src/index.ts @@ -0,0 +1,15 @@ +import * as Gitbeaker from '@gitbeaker/core'; +import { Requester } from './KyRequester'; + +const output = {}; + +Object.keys(Gitbeaker).forEach(name => { + output[name] = args => + new Gitbeaker[name]({ + requester: Requester, + ...args, + }); +}); + +/* eslint-disable */ +export default output; diff --git a/packages/gitbeaker-browser/test/unit/KyRequester.ts b/packages/gitbeaker-browser/test/unit/KyRequester.ts new file mode 100644 index 00000000..19d5f7c0 --- /dev/null +++ b/packages/gitbeaker-browser/test/unit/KyRequester.ts @@ -0,0 +1,176 @@ +import ky from 'ky'; +import fetch from 'node-fetch'; +import { processBody, handler, defaultRequest } from '../../src/KyRequester'; + +// Set globals for testing purposes +if (!global.fetch) { + global.fetch = fetch; +} + +if (!global.Headers) { + global.Headers = fetch.Headers; +} + +jest.mock('ky'); + +describe('processBody', () => { + it('should return a json object if type is application/json', async () => { + const output = await processBody({ + json() { + return Promise.resolve({ test: 5 }); + }, + headers: { + get() { + return 'application/json'; + }, + }, + }); + + expect(output).toMatchObject({ test: 5 }); + }); + + it('should return a buffer if type is octet-stream, binary, or gzip', async () => { + const output = [ + processBody({ + blob() { + return Promise.resolve('test'); + }, + headers: { + get() { + return 'application/octet-stream'; + }, + }, + }), + processBody({ + blob() { + return Promise.resolve('test'); + }, + headers: { + get() { + return 'binary/octet-stream'; + }, + }, + }), + processBody({ + blob() { + return Promise.resolve('test'); + }, + headers: { + get() { + return 'application/gzip'; + }, + }, + }), + ]; + + const fulfilled = await Promise.all(output); + + fulfilled.forEach(o => expect(o).toBeInstanceOf(Buffer)); + }); + + it('should return a text body given when presented with an unknown content-type', async () => { + const output = await processBody({ + text() { + return Promise.resolve('6'); + }, + headers: { + get() { + return 'fake'; + }, + }, + }); + + expect(output).toBe('6'); + }); + + it('should return a empty string when presented with an unknown content-type and undefined body', async () => { + const output = await processBody({ + text() { + return Promise.resolve(null); + }, + headers: { + get() { + return 'fake'; + }, + }, + }); + + expect(output).toBe(''); + }); +}); + +describe('handler', () => { + it('should return an error with a description when response has an error prop', async () => { + ky.mockImplementationOnce(() => { + const e = { + response: { + json() { + return Promise.resolve({ error: 'msg' }); + }, + }, + }; + return Promise.reject(e); + }); + + await expect(handler('http://test.com', {})).rejects.toContainEntry(['description', 'msg']); + }); + + it('should return an error with a description when response has an message prop', async () => { + ky.mockImplementationOnce(() => { + const e = { + response: { + json() { + return Promise.resolve({ message: 'msg' }); + }, + }, + }; + return Promise.reject(e); + }); + + await expect(handler('http://test.com', {})).rejects.toContainEntry(['description', 'msg']); + }); + + it('should return correct properties if request is valid', async () => { + ky.mockImplementationOnce(() => ({ + status: 404, + headers: { + get() { + return 'application/json'; + }, + entries() { + return [['token', '1234']]; + }, + }, + json() { + return Promise.resolve({}); + }, + })); + + const output = await handler('http://test.com', {}); + + expect(output).toMatchObject({ + body: {}, + headers: {}, + status: 404, + }); + }); +}); + +describe('defaultRequest', () => { + const service = { + headers: { test: '5' }, + url: 'testurl', + rejectUnauthorized: false, + requestTimeout: 50, + }; + + it('should stringify body if it isnt of type FormData', async () => { + const testBody = { test: 6 }; + const { body, headers } = defaultRequest(service, { + body: testBody, + }); + + expect(headers).toBeInstanceOf(Headers); + expect(body).toBe(JSON.stringify(testBody)); + }); +}); diff --git a/packages/gitbeaker-browser/tsconfig.json b/packages/gitbeaker-browser/tsconfig.json new file mode 100644 index 00000000..8da2efa0 --- /dev/null +++ b/packages/gitbeaker-browser/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declaration": true, + "declarationDir": "dist/types" + }, + "include": ["src"] +} diff --git a/packages/gitbeaker-cli/package.json b/packages/gitbeaker-cli/package.json new file mode 100644 index 00000000..9d10cd2c --- /dev/null +++ b/packages/gitbeaker-cli/package.json @@ -0,0 +1,53 @@ +{ + "name": "@gitbeaker/cli", + "description": "Full NodeJS CLI implementation of the GitLab API.", + "version": "15.0.0", + "author": { + "name": "Justin Dalrymple" + }, + "bin": { + "gitbeaker": "dist/index.js" + }, + "bugs": { + "url": "https://github.com/jdalrymple/gitbeaker/issues" + }, + "dependencies": { + "@gitbeaker/core": "15.0.0", + "@gitbeaker/node": "15.0.0", + "chalk": "^3.0.0", + "ora": "^4.0.3", + "sywac": "^1.2.2", + "xcase": "^2.0.1" + }, + "devDependencies": { + "@rollup/plugin-json": "^4.0.1", + "rollup": "^1.31.0", + "rollup-plugin-preserve-shebangs": "^0.1.2", + "rollup-plugin-terser": "^5.2.0", + "rollup-plugin-typescript2": "^0.25.3", + "strip-ansi": "^6.0.0", + "typescript": "^3.7.5" + }, + "engines": { + "node": ">=10.0.0" + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/jdalrymple/gitbeaker#readme", + "keywords": [ + "api", + "cli", + "gitbeaker", + "gitlab", + "got" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/jdalrymple/gitbeaker" + }, + "scripts": { + "build": "tsc && rollup -c" + } +} diff --git a/packages/gitbeaker-cli/rollup.config.js b/packages/gitbeaker-cli/rollup.config.js new file mode 100644 index 00000000..8473560a --- /dev/null +++ b/packages/gitbeaker-cli/rollup.config.js @@ -0,0 +1,14 @@ +import json from '@rollup/plugin-json'; +import { preserveShebangs } from 'rollup-plugin-preserve-shebangs'; +import pkg from './package.json'; +import { commonConfig, commonPlugins } from '../../rollup.config.js'; + +export default { + ...commonConfig, + external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], + output: { + file: pkg.bin.gitbeaker, + format: 'cjs', + }, + plugins: [...commonPlugins, json(), preserveShebangs()], +}; diff --git a/packages/gitbeaker-cli/tsconfig.json b/packages/gitbeaker-cli/tsconfig.json new file mode 100644 index 00000000..b1224f98 --- /dev/null +++ b/packages/gitbeaker-cli/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist" + }, + "include": ["src"] +} diff --git a/packages/gitbeaker-core/package.json b/packages/gitbeaker-core/package.json new file mode 100644 index 00000000..a3cf8fa9 --- /dev/null +++ b/packages/gitbeaker-core/package.json @@ -0,0 +1,54 @@ +{ + "name": "@gitbeaker/core", + "description": "Core API implementation of the GitLab API. Supports Promises, Async/Await.", + "version": "15.0.0", + "author": { + "name": "Justin Dalrymple" + }, + "bugs": { + "url": "https://github.com/jdalrymple/gitbeaker/issues" + }, + "dependencies": { + "@gitbeaker/requester-utils": "15.0.0", + "form-data": "^3.0.0", + "li": "^1.3.0", + "xcase": "^2.0.1" + }, + "devDependencies": { + "@types/node": "^13.7.0", + "esm": "^3.2.25", + "fs-extra": "^8.1.0", + "get-param-names": "github:jdalrymple/get-param-names#1-improve-functionality", + "rollup": "^1.31.0", + "rollup-plugin-terser": "^5.2.0", + "rollup-plugin-typescript2": "^0.25.3", + "ts-node": "^8.6.2", + "typescript": "^3.7.5" + }, + "engines": { + "node": ">=10.0.0" + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/jdalrymple/gitbeaker#readme", + "keywords": [ + "api", + "es5", + "es6", + "gitlab" + ], + "license": "MIT", + "main": "dist/index.js", + "module": "dist/index.es.js", + "repository": { + "type": "git", + "url": "https://github.com/jdalrymple/gitbeaker" + }, + "scripts": { + "build:self": "tsc && rollup -c", + "build": "yarn run generate-map && yarn run build:self", + "generate-map": "ESM_DISABLE_CACHE=true TS_NODE_PROJECT=scripts/tsconfig.json node -r esm -r ts-node/register scripts/generate.ts" + }, + "types": "dist/types/index.d.ts" +} diff --git a/packages/gitbeaker-core/rollup.config.js b/packages/gitbeaker-core/rollup.config.js new file mode 100644 index 00000000..09a5a3a1 --- /dev/null +++ b/packages/gitbeaker-core/rollup.config.js @@ -0,0 +1,18 @@ +import pkg from './package.json'; +import { commonConfig, commonPlugins } from '../../rollup.config'; + +export default { + ...commonConfig, + external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], + output: [ + { + file: pkg.main, // CommonJS (for Node) (for bundlers) build. + format: 'cjs', + }, + { + file: pkg.module, // ES module (for bundlers) build. + format: 'es', + }, + ], + plugins: commonPlugins, +}; diff --git a/src/bin/generate.ts b/packages/gitbeaker-core/scripts/generate.ts similarity index 83% rename from src/bin/generate.ts rename to packages/gitbeaker-core/scripts/generate.ts index f5ed218c..b33390ea 100644 --- a/src/bin/generate.ts +++ b/packages/gitbeaker-core/scripts/generate.ts @@ -1,7 +1,7 @@ import getParamNames from 'get-param-names'; -import { outputJsonSync, removeSync } from 'fs-extra'; -import * as core from '../core'; -import { BaseService } from '../core/infrastructure'; +import { outputJsonSync } from 'fs-extra'; +import * as Gitbeaker from '../src'; +import { BaseService } from '../src/infrastructure'; function isGetter(x, name) { return (Object.getOwnPropertyDescriptor(x, name) || {}).get; @@ -39,10 +39,10 @@ function buildMap() { const map = {}; const baseArgs = Object.keys(getParamNames(BaseService)[0]); - for (const [name, service] of Object.entries(core)) { + for (const [name, service] of Object.entries(Gitbeaker as object)) { if (name.includes('Bundle') || name === 'Gitlab') continue; - const s = new service(); + const s = new service({ requester: {} }); map[name] = [{ name: 'constructor', args: baseArgs }]; @@ -59,5 +59,3 @@ function buildMap() { // Generate the services map outputJsonSync('./dist/map.json', buildMap()); - -removeSync('./temp'); diff --git a/tsconfig.cli.json b/packages/gitbeaker-core/scripts/tsconfig.json similarity index 80% rename from tsconfig.cli.json rename to packages/gitbeaker-core/scripts/tsconfig.json index cf95ff93..a996a1e8 100644 --- a/tsconfig.cli.json +++ b/packages/gitbeaker-core/scripts/tsconfig.json @@ -1,8 +1,6 @@ { - "files": ["src/bin/generate.ts"], "compilerOptions": { "esModuleInterop": true, - "outDir": "temp", "allowJs": true, "target": "es6", "module": "esnext", diff --git a/src/core/index.ts b/packages/gitbeaker-core/src/index.ts similarity index 94% rename from src/core/index.ts rename to packages/gitbeaker-core/src/index.ts index 7cf402c9..f2120696 100644 --- a/src/core/index.ts +++ b/packages/gitbeaker-core/src/index.ts @@ -1,9 +1,11 @@ import { bundler } from './infrastructure'; import * as APIServices from './services'; -// All separately +/* -------------- Single Services ------------- */ export * from './services'; +/* ------------------ Bundles ----------------- */ + // Groups export const GroupsBundle = bundler({ Groups: APIServices.Groups, @@ -81,12 +83,13 @@ export const ProjectsBundle = bundler({ Services: APIServices.Services, Tags: APIServices.Tags, Triggers: APIServices.Triggers, + VulnerabilityFindings: APIServices.VulnerabilityFindings, }); // All initialized export const Gitlab = bundler(APIServices); -// Types +/* ---------------- Bundles Types-------------- */ export type UsersBundle = InstanceType; export type GroupsBundle = InstanceType; export type ProjectsBundle = InstanceType; diff --git a/src/core/infrastructure/BaseService.ts b/packages/gitbeaker-core/src/infrastructure/BaseService.ts similarity index 79% rename from src/core/infrastructure/BaseService.ts rename to packages/gitbeaker-core/src/infrastructure/BaseService.ts index 2add45b6..21e3e0b1 100644 --- a/src/core/infrastructure/BaseService.ts +++ b/packages/gitbeaker-core/src/infrastructure/BaseService.ts @@ -1,12 +1,4 @@ -import { KyRequester } from './KyRequester'; - -export interface Requester { - get: Function; - post: Function; - put: Function; - delete: Function; - stream?: Function; -} +import { RequesterType } from '@gitbeaker/requester-utils'; export interface BaseServiceOptions { oauthToken?: string; @@ -17,7 +9,7 @@ export interface BaseServiceOptions { version?: 3 | 4; rejectUnauthorized?: boolean; camelize?: boolean; - requester?: Requester; + requester?: RequesterType; requestTimeout?: number; profileToken?: string; sudo?: string | number; @@ -27,7 +19,7 @@ export interface BaseServiceOptions { export class BaseService { public readonly url: string; - public readonly requester: Requester; + public readonly requester: RequesterType; public readonly requestTimeout: number; @@ -43,17 +35,21 @@ export class BaseService { oauthToken, sudo, profileToken, + requester, profileMode = 'execution', host = 'https://gitlab.com', url = '', version = 4, camelize = false, rejectUnauthorized = true, - requester = KyRequester as Requester, requestTimeout = 300000, }: BaseServiceOptions = {}) { + if (!requester) throw new ReferenceError('Requester must be passed'); + this.url = [host, 'api', `v${version}`, url].join('/'); - this.headers = {}; + this.headers = { + 'user-agent': 'gitbeaker', + }; this.rejectUnauthorized = rejectUnauthorized; this.camelize = camelize; this.requester = requester; @@ -67,8 +63,7 @@ export class BaseService { // Profiling if (profileToken) { this.headers['X-Profile-Token'] = profileToken; - - if (profileMode) this.headers['X-Profile-Mode'] = profileMode; + this.headers['X-Profile-Mode'] = profileMode; } // Set sudo diff --git a/src/core/infrastructure/RequestHelper.ts b/packages/gitbeaker-core/src/infrastructure/RequestHelper.ts similarity index 62% rename from src/core/infrastructure/RequestHelper.ts rename to packages/gitbeaker-core/src/infrastructure/RequestHelper.ts index 1669d393..1635d6c3 100644 --- a/src/core/infrastructure/RequestHelper.ts +++ b/packages/gitbeaker-core/src/infrastructure/RequestHelper.ts @@ -6,6 +6,10 @@ export interface Sudo { sudo?: string | number; } +export interface ShowExpanded { + showExpanded?: boolean; +} + export interface PaginationOptions { total: number; next: number | null; @@ -20,31 +24,40 @@ export interface BaseRequestOptions extends Sudo { [key: string]: any; } -export interface PaginatedRequestOptions extends BaseRequestOptions { - showPagination?: boolean; +export interface PaginatedRequestOptions extends BaseRequestOptions, ShowExpanded { maxPages?: number; page?: number; perPage?: number; } -export type PaginationResponse = { data: object[]; pagination: PaginationOptions }; -export type GetResponse = PaginationResponse | object | object[]; -export type PostResponse = object; -export type PutResponse = object; -export type DelResponse = object; +export interface ExpandedResponse { + data: object; + headers: object; + status: number; +} + +export interface PaginationResponse { + data: object[]; + pagination: PaginationOptions; +} + +export type GetResponse = PaginationResponse | ExpandedResponse | object | object[]; +export type PostResponse = ExpandedResponse | object; +export type PutResponse = ExpandedResponse | object; +export type DelResponse = ExpandedResponse | object; async function get( service: BaseService, endpoint: string, options: PaginatedRequestOptions = {}, ): Promise { - const { showPagination, maxPages, sudo, ...query } = options; + const { showExpanded, maxPages, sudo, ...query } = options; const response = await service.requester.get(service, endpoint, { query: query || {}, sudo, }); - const { headers } = response; + const { headers, status } = response; let { body } = response; let pagination = { total: parseInt(headers['x-total'], 10), @@ -68,14 +81,27 @@ async function get( const more = (await get(service, next.replace(regex, ''), { maxPages, sudo, - showPagination: true, + showExpanded: true, })) as PaginationResponse; pagination = more.pagination; body = [...body, ...more.data]; } - return (query.page || body.length > 0) && showPagination ? { data: body, pagination } : body; + // If expanded version is not requested, return body + if (!showExpanded) return body; + + // Else build the expanded response + const output = { data: body } as Record; + + if (body.length > 0 || query.page) { + output.pagination = pagination; + } else { + output.headers = headers; + output.status = status; + } + + return output; } function stream(service: BaseService, endpoint: string, options: BaseRequestOptions = {}) { @@ -93,14 +119,14 @@ async function post( endpoint: string, options: BaseRequestOptions = {}, ): Promise { - const { sudo, form, ...body } = options; + const { sudo, form, showExpanded, ...body } = options; - const response = await service.requester.post(service, endpoint, { + const r = await service.requester.post(service, endpoint, { body: form || body, sudo, }); - return response.body; + return showExpanded ? { data: r.body, status: r.status, headers: r.headers } : r.body; } async function put( @@ -108,13 +134,13 @@ async function put( endpoint: string, options: BaseRequestOptions = {}, ): Promise { - const { sudo, ...body } = options; - const response = await service.requester.put(service, endpoint, { + const { sudo, showExpanded, ...body } = options; + const r = await service.requester.put(service, endpoint, { body, sudo, }); - return response.body; + return showExpanded ? { data: r.body, status: r.status, headers: r.headers } : r.body; } async function del( @@ -122,13 +148,13 @@ async function del( endpoint: string, options: BaseRequestOptions = {}, ): Promise { - const { sudo, ...query } = options; - const response = await service.requester.delete(service, endpoint, { + const { sudo, showExpanded, ...query } = options; + const r = await service.requester.delete(service, endpoint, { query, sudo, }); - return response.body; + return showExpanded ? { data: r.body, status: r.status, headers: r.headers } : r.body; } export const RequestHelper = { diff --git a/src/core/infrastructure/Utils.ts b/packages/gitbeaker-core/src/infrastructure/Utils.ts similarity index 74% rename from src/core/infrastructure/Utils.ts rename to packages/gitbeaker-core/src/infrastructure/Utils.ts index b790a41d..585d7b4b 100644 --- a/src/core/infrastructure/Utils.ts +++ b/packages/gitbeaker-core/src/infrastructure/Utils.ts @@ -1,5 +1,4 @@ /* eslint @typescript-eslint/no-explicit-any: 0 */ - interface Constructor { new (...args: any): any; } @@ -19,3 +18,12 @@ export function bundler; } + +export function appendFormFromObject(object) { + const form = new FormData(); + + Object.entries(object).forEach(([k, v]) => { + if (Array.isArray(v)) form.append(k, v[0], v[1]); + else form.append(k, v as any); + }); +} diff --git a/src/core/infrastructure/index.ts b/packages/gitbeaker-core/src/infrastructure/index.ts similarity index 68% rename from src/core/infrastructure/index.ts rename to packages/gitbeaker-core/src/infrastructure/index.ts index 3e3bd459..b4b018a1 100644 --- a/src/core/infrastructure/index.ts +++ b/packages/gitbeaker-core/src/infrastructure/index.ts @@ -1,4 +1,3 @@ export * from './BaseService'; export { bundler } from './Utils'; -export { KyRequester } from './KyRequester'; export * from './RequestHelper'; diff --git a/src/core/services/ApplicationSettings.ts b/packages/gitbeaker-core/src/services/ApplicationSettings.ts similarity index 100% rename from src/core/services/ApplicationSettings.ts rename to packages/gitbeaker-core/src/services/ApplicationSettings.ts diff --git a/src/core/services/Branches.ts b/packages/gitbeaker-core/src/services/Branches.ts similarity index 100% rename from src/core/services/Branches.ts rename to packages/gitbeaker-core/src/services/Branches.ts diff --git a/src/core/services/BroadcastMessages.ts b/packages/gitbeaker-core/src/services/BroadcastMessages.ts similarity index 100% rename from src/core/services/BroadcastMessages.ts rename to packages/gitbeaker-core/src/services/BroadcastMessages.ts diff --git a/src/core/services/CommitDiscussions.ts b/packages/gitbeaker-core/src/services/CommitDiscussions.ts similarity index 81% rename from src/core/services/CommitDiscussions.ts rename to packages/gitbeaker-core/src/services/CommitDiscussions.ts index 382a26f3..95b4cad2 100644 --- a/src/core/services/CommitDiscussions.ts +++ b/packages/gitbeaker-core/src/services/CommitDiscussions.ts @@ -2,7 +2,7 @@ import { ResourceDiscussions } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class CommitDiscussions extends ResourceDiscussions { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', 'commits', options); } } diff --git a/src/core/services/Commits.ts b/packages/gitbeaker-core/src/services/Commits.ts similarity index 83% rename from src/core/services/Commits.ts rename to packages/gitbeaker-core/src/services/Commits.ts index 03ec6c28..6f6ec687 100644 --- a/src/core/services/Commits.ts +++ b/packages/gitbeaker-core/src/services/Commits.ts @@ -6,6 +6,39 @@ import { Sudo, } from '../infrastructure'; +export type CommitSchema = CommitSchemaDefault | CommitSchemaCamelized; + +// As of GitLab v12.6.2 +export interface CommitSchemaDefault { + id: string; + short_id: string; + created_at: Date; + parent_ids?: string[]; + title: string; + message: string; + author_name: string; + author_email: string; + authored_date?: Date; + committer_name?: string; + committer_email?: string; + committed_date?: Date; +} + +export interface CommitSchemaCamelized { + id: string; + shortId: string; + createdAt: Date; + parentIds?: string[]; + title: string; + message: string; + authorName: string; + authorEmail: string; + authoredDate?: Date; + committerName?: string; + committerEmail?: string; + committedDate?: Date; +} + interface CommitAction { /** The action to perform */ action: 'create' | 'delete' | 'move' | 'update'; diff --git a/src/core/services/ContainerRegistry.ts b/packages/gitbeaker-core/src/services/ContainerRegistry.ts similarity index 100% rename from src/core/services/ContainerRegistry.ts rename to packages/gitbeaker-core/src/services/ContainerRegistry.ts diff --git a/src/core/services/DeployKeys.ts b/packages/gitbeaker-core/src/services/DeployKeys.ts similarity index 100% rename from src/core/services/DeployKeys.ts rename to packages/gitbeaker-core/src/services/DeployKeys.ts diff --git a/packages/gitbeaker-core/src/services/Deployments.ts b/packages/gitbeaker-core/src/services/Deployments.ts new file mode 100644 index 00000000..7f6da667 --- /dev/null +++ b/packages/gitbeaker-core/src/services/Deployments.ts @@ -0,0 +1,73 @@ +import { BaseService, RequestHelper, PaginatedRequestOptions, Sudo } from '../infrastructure'; + +import { CommitSchema } from './Commits'; +import { PipelineSchema } from './Pipelines'; +import { UserSchema } from './Users'; +import { RunnerSchema } from './Runners'; + +export type DeploymentStatus = 'created' | 'running' | 'success' | 'failed' | 'canceled'; + +// Ref: https://docs.gitlab.com/12.6/ee/api/deployments.html#list-project-deployments +export interface DeploymentSchema { + id: number; + iid: number; + ref: string; + sha: string; + user: UserSchema; +} + +export type Deployable = DeployableDefault | DeployableCamelized; + +export interface DeployableDefault { + id: number; + ref: string; + name: string; + runner?: RunnerSchema; + stage?: string; + started_at?: Date; + status?: DeploymentStatus; + tag: boolean; + commit?: CommitSchema; + coverage?: string; + created_at?: Date; + finished_at?: Date; + user?: UserSchema; + pipeline?: PipelineSchema; +} + +export interface DeployableCamelized { + id: number; + ref: string; + name: string; + runner?: RunnerSchema; + stage?: string; + startedAt?: Date; + status?: DeploymentStatus; + tag: boolean; + commit?: CommitSchema; + coverage?: string; + createdAt?: Date; + finishedAt?: Date; + user?: UserSchema; + pipeline?: PipelineSchema; +} + +export class Deployments extends BaseService { + all(projectId: string | number, options?: PaginatedRequestOptions) { + const pId = encodeURIComponent(projectId); + + return RequestHelper.get(this, `projects/${pId}/deployments`, options); + } + + show(projectId: string | number, deploymentId: number, options?: Sudo) { + const [pId, dId] = [projectId, deploymentId].map(encodeURIComponent); + + return RequestHelper.get(this, `projects/${pId}/deployments/${dId}`, options); + } + + mergeRequests(projectId: string | number, deploymentId: number, options?: Sudo) { + const [pId, dId] = [projectId, deploymentId].map(encodeURIComponent); + + return RequestHelper.get(this, `projects/${pId}/deployments/${dId}/merge_requests`, options); + } +} diff --git a/src/core/services/Environments.ts b/packages/gitbeaker-core/src/services/Environments.ts similarity index 51% rename from src/core/services/Environments.ts rename to packages/gitbeaker-core/src/services/Environments.ts index bb6b6806..519fee15 100644 --- a/src/core/services/Environments.ts +++ b/packages/gitbeaker-core/src/services/Environments.ts @@ -5,18 +5,62 @@ import { RequestHelper, Sudo, } from '../infrastructure'; +import { DeploymentSchema } from './Deployments'; +import { ProjectSchemaDefault, ProjectSchemaCamelized } from './Projects'; + +// ref: https://docs.gitlab.com/12.6/ee/api/environments.html#list-environments +export type EnvironmentSchema = EnvironmentSchemaDefault | EnvironmentSchemaCamelized; + +export interface EnvironmentSchemaDefault { + id: number; + name: string; + slug?: string; + external_url?: string; + project?: ProjectSchemaDefault; + state?: string; +} + +export interface EnvironmentSchemaCamelized { + id: number; + name: string; + slug?: string; + externalUrl?: string; + project?: ProjectSchemaCamelized; + state?: string; +} + +export type EnvironmentDetailSchema = + | EnvironmentDetailSchemaDefault + | EnvironmentDetailSchemaCamelized; + +export interface EnvironmentDetailSchemaDefault extends EnvironmentSchemaDefault { + last_deployment?: DeploymentSchema; + deployable?: DeploymentSchema; +} + +export interface EnvironmentDetailSchemaCamelized extends EnvironmentSchemaCamelized { + lastDeployment?: DeploymentSchema; + deployable?: DeploymentSchema; +} export class Environments extends BaseService { - all(projectId: string | number, options?: PaginatedRequestOptions) { + all(projectId: string | number, options?: PaginatedRequestOptions): Promise { const pId = encodeURIComponent(projectId); - return RequestHelper.get(this, `projects/${pId}/environments`, options); + return RequestHelper.get(this, `projects/${pId}/environments`, options) as Promise< + EnvironmentSchema[] + >; } - show(projectId: string | number, environmentId: number, options?: Sudo) { + show( + projectId: string | number, + environmentId: number, + options?: Sudo, + ): Promise { const [pId, eId] = [projectId, environmentId].map(encodeURIComponent); - - return RequestHelper.get(this, `projects/${pId}/environments/${eId}`, options); + return RequestHelper.get(this, `projects/${pId}/environments/${eId}`, options) as Promise< + EnvironmentDetailSchema + >; } create(projectId: string | number, options?: BaseRequestOptions) { diff --git a/src/core/services/EpicDiscussions.ts b/packages/gitbeaker-core/src/services/EpicDiscussions.ts similarity index 80% rename from src/core/services/EpicDiscussions.ts rename to packages/gitbeaker-core/src/services/EpicDiscussions.ts index c1c6df72..f4ea1b0e 100644 --- a/src/core/services/EpicDiscussions.ts +++ b/packages/gitbeaker-core/src/services/EpicDiscussions.ts @@ -2,7 +2,7 @@ import { ResourceDiscussions } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class EpicDiscussions extends ResourceDiscussions { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', 'epics', options); } } diff --git a/src/core/services/EpicIssues.ts b/packages/gitbeaker-core/src/services/EpicIssues.ts similarity index 100% rename from src/core/services/EpicIssues.ts rename to packages/gitbeaker-core/src/services/EpicIssues.ts diff --git a/src/core/services/EpicNotes.ts b/packages/gitbeaker-core/src/services/EpicNotes.ts similarity index 79% rename from src/core/services/EpicNotes.ts rename to packages/gitbeaker-core/src/services/EpicNotes.ts index d2ecf993..c461baf6 100644 --- a/src/core/services/EpicNotes.ts +++ b/packages/gitbeaker-core/src/services/EpicNotes.ts @@ -2,7 +2,7 @@ import { ResourceNotes } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class EpicNotes extends ResourceNotes { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', 'epics', options); } } diff --git a/src/core/services/Epics.ts b/packages/gitbeaker-core/src/services/Epics.ts similarity index 100% rename from src/core/services/Epics.ts rename to packages/gitbeaker-core/src/services/Epics.ts diff --git a/src/core/services/Events.ts b/packages/gitbeaker-core/src/services/Events.ts similarity index 100% rename from src/core/services/Events.ts rename to packages/gitbeaker-core/src/services/Events.ts diff --git a/src/core/services/FeatureFlags.ts b/packages/gitbeaker-core/src/services/FeatureFlags.ts similarity index 100% rename from src/core/services/FeatureFlags.ts rename to packages/gitbeaker-core/src/services/FeatureFlags.ts diff --git a/src/core/services/GeoNodes.ts b/packages/gitbeaker-core/src/services/GeoNodes.ts similarity index 100% rename from src/core/services/GeoNodes.ts rename to packages/gitbeaker-core/src/services/GeoNodes.ts diff --git a/src/core/services/GitLabCIYMLTemplates.ts b/packages/gitbeaker-core/src/services/GitLabCIYMLTemplates.ts similarity index 80% rename from src/core/services/GitLabCIYMLTemplates.ts rename to packages/gitbeaker-core/src/services/GitLabCIYMLTemplates.ts index 2791ae1c..1fcf094e 100644 --- a/src/core/services/GitLabCIYMLTemplates.ts +++ b/packages/gitbeaker-core/src/services/GitLabCIYMLTemplates.ts @@ -2,7 +2,7 @@ import { ResourceTemplates } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GitLabCIYMLTemplates extends ResourceTemplates { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('gitlab_ci_ymls', options); } } diff --git a/src/core/services/GitignoreTemplates.ts b/packages/gitbeaker-core/src/services/GitignoreTemplates.ts similarity index 80% rename from src/core/services/GitignoreTemplates.ts rename to packages/gitbeaker-core/src/services/GitignoreTemplates.ts index e59413c0..d5f051ca 100644 --- a/src/core/services/GitignoreTemplates.ts +++ b/packages/gitbeaker-core/src/services/GitignoreTemplates.ts @@ -2,7 +2,7 @@ import { ResourceTemplates } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GitignoreTemplates extends ResourceTemplates { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('gitignores', options); } } diff --git a/src/core/services/GroupAccessRequests.ts b/packages/gitbeaker-core/src/services/GroupAccessRequests.ts similarity index 81% rename from src/core/services/GroupAccessRequests.ts rename to packages/gitbeaker-core/src/services/GroupAccessRequests.ts index b3251844..6af28cf7 100644 --- a/src/core/services/GroupAccessRequests.ts +++ b/packages/gitbeaker-core/src/services/GroupAccessRequests.ts @@ -2,7 +2,7 @@ import { ResourceAccessRequests } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GroupAccessRequests extends ResourceAccessRequests { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/GroupBadges.ts b/packages/gitbeaker-core/src/services/GroupBadges.ts similarity index 79% rename from src/core/services/GroupBadges.ts rename to packages/gitbeaker-core/src/services/GroupBadges.ts index 62a6c2aa..435f2c0f 100644 --- a/src/core/services/GroupBadges.ts +++ b/packages/gitbeaker-core/src/services/GroupBadges.ts @@ -2,7 +2,7 @@ import { ResourceBadges } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GroupBadges extends ResourceBadges { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/GroupCustomAttributes.ts b/packages/gitbeaker-core/src/services/GroupCustomAttributes.ts similarity index 81% rename from src/core/services/GroupCustomAttributes.ts rename to packages/gitbeaker-core/src/services/GroupCustomAttributes.ts index 4163e062..f7415f39 100644 --- a/src/core/services/GroupCustomAttributes.ts +++ b/packages/gitbeaker-core/src/services/GroupCustomAttributes.ts @@ -2,7 +2,7 @@ import { ResourceCustomAttributes } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GroupCustomAttributes extends ResourceCustomAttributes { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/GroupIssueBoards.ts b/packages/gitbeaker-core/src/services/GroupIssueBoards.ts similarity index 80% rename from src/core/services/GroupIssueBoards.ts rename to packages/gitbeaker-core/src/services/GroupIssueBoards.ts index 7a58ecc5..fb6fa993 100644 --- a/src/core/services/GroupIssueBoards.ts +++ b/packages/gitbeaker-core/src/services/GroupIssueBoards.ts @@ -2,7 +2,7 @@ import { ResourceIssueBoards } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GroupIssueBoards extends ResourceIssueBoards { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/GroupLabels.ts b/packages/gitbeaker-core/src/services/GroupLabels.ts similarity index 79% rename from src/core/services/GroupLabels.ts rename to packages/gitbeaker-core/src/services/GroupLabels.ts index a1c6a4bb..a4876a91 100644 --- a/src/core/services/GroupLabels.ts +++ b/packages/gitbeaker-core/src/services/GroupLabels.ts @@ -2,7 +2,7 @@ import { BaseServiceOptions } from '../infrastructure'; import { ResourceLabels } from '../templates'; export class GroupLabels extends ResourceLabels { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/GroupMembers.ts b/packages/gitbeaker-core/src/services/GroupMembers.ts similarity index 79% rename from src/core/services/GroupMembers.ts rename to packages/gitbeaker-core/src/services/GroupMembers.ts index 1270abb5..1c50704e 100644 --- a/src/core/services/GroupMembers.ts +++ b/packages/gitbeaker-core/src/services/GroupMembers.ts @@ -2,7 +2,7 @@ import { ResourceMembers } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GroupMembers extends ResourceMembers { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/GroupMilestones.ts b/packages/gitbeaker-core/src/services/GroupMilestones.ts similarity index 80% rename from src/core/services/GroupMilestones.ts rename to packages/gitbeaker-core/src/services/GroupMilestones.ts index bd7c07c6..c06477da 100644 --- a/src/core/services/GroupMilestones.ts +++ b/packages/gitbeaker-core/src/services/GroupMilestones.ts @@ -2,7 +2,7 @@ import { ResourceMilestones } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GroupMilestones extends ResourceMilestones { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/GroupProjects.ts b/packages/gitbeaker-core/src/services/GroupProjects.ts similarity index 100% rename from src/core/services/GroupProjects.ts rename to packages/gitbeaker-core/src/services/GroupProjects.ts diff --git a/src/core/services/GroupVariables.ts b/packages/gitbeaker-core/src/services/GroupVariables.ts similarity index 79% rename from src/core/services/GroupVariables.ts rename to packages/gitbeaker-core/src/services/GroupVariables.ts index 261746db..735d2c80 100644 --- a/src/core/services/GroupVariables.ts +++ b/packages/gitbeaker-core/src/services/GroupVariables.ts @@ -2,7 +2,7 @@ import { ResourceVariables } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class GroupVariables extends ResourceVariables { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('groups', options); } } diff --git a/src/core/services/IssueAwardEmojis.ts b/packages/gitbeaker-core/src/services/IssueAwardEmojis.ts similarity index 80% rename from src/core/services/IssueAwardEmojis.ts rename to packages/gitbeaker-core/src/services/IssueAwardEmojis.ts index 56d71f15..a0ec1542 100644 --- a/src/core/services/IssueAwardEmojis.ts +++ b/packages/gitbeaker-core/src/services/IssueAwardEmojis.ts @@ -2,7 +2,7 @@ import { ResourceAwardEmojis } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class IssueAwardEmojis extends ResourceAwardEmojis { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('issues', options); } } diff --git a/src/core/services/IssueDiscussions.ts b/packages/gitbeaker-core/src/services/IssueDiscussions.ts similarity index 81% rename from src/core/services/IssueDiscussions.ts rename to packages/gitbeaker-core/src/services/IssueDiscussions.ts index 8b111fa4..01ed62cd 100644 --- a/src/core/services/IssueDiscussions.ts +++ b/packages/gitbeaker-core/src/services/IssueDiscussions.ts @@ -2,7 +2,7 @@ import { ResourceDiscussions } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class IssueDiscussions extends ResourceDiscussions { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', 'issues', options); } } diff --git a/src/core/services/IssueNotes.ts b/packages/gitbeaker-core/src/services/IssueNotes.ts similarity index 79% rename from src/core/services/IssueNotes.ts rename to packages/gitbeaker-core/src/services/IssueNotes.ts index 5213e775..48c59aad 100644 --- a/src/core/services/IssueNotes.ts +++ b/packages/gitbeaker-core/src/services/IssueNotes.ts @@ -2,7 +2,7 @@ import { ResourceNotes } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class IssueNotes extends ResourceNotes { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', 'issues', options); } } diff --git a/src/core/services/Issues.ts b/packages/gitbeaker-core/src/services/Issues.ts similarity index 95% rename from src/core/services/Issues.ts rename to packages/gitbeaker-core/src/services/Issues.ts index e13bd984..85a29069 100644 --- a/src/core/services/Issues.ts +++ b/packages/gitbeaker-core/src/services/Issues.ts @@ -6,6 +6,8 @@ import { Sudo, } from '../infrastructure'; +type ProjectOrGroup = { projectId: string | number } | { groupId: string | number } | {}; + export class Issues extends BaseService { addSpentTime(projectId: string | number, issueId: number, duration: string, options?: Sudo) { const [pId, iId] = [projectId, issueId].map(encodeURIComponent); @@ -25,12 +27,7 @@ export class Issues extends BaseService { }); } - all({ - projectId, - groupId, - ...options - }: ({ projectId?: string | number } | { groupId?: string | number } | {}) & - PaginatedRequestOptions = {}) { + all({ projectId, groupId, ...options }: ProjectOrGroup & PaginatedRequestOptions = {}) { let url; if (projectId) { diff --git a/src/core/services/IssuesStatistics.ts b/packages/gitbeaker-core/src/services/IssuesStatistics.ts similarity index 71% rename from src/core/services/IssuesStatistics.ts rename to packages/gitbeaker-core/src/services/IssuesStatistics.ts index 6198f146..fd4b8d14 100644 --- a/src/core/services/IssuesStatistics.ts +++ b/packages/gitbeaker-core/src/services/IssuesStatistics.ts @@ -1,12 +1,9 @@ import { BaseService, RequestHelper, BaseRequestOptions } from '../infrastructure'; +type ProjectOrGroup = { projectId: string | number } | { groupId: string | number } | {}; + export class IssuesStatistics extends BaseService { - all({ - projectId, - groupId, - ...options - }: ({ projectId?: string | number } | { groupId?: string | number } | {}) & - BaseRequestOptions = {}) { + all({ projectId, groupId, ...options }: ProjectOrGroup & BaseRequestOptions = {}) { let url; if (projectId) { diff --git a/src/core/services/Jobs.ts b/packages/gitbeaker-core/src/services/Jobs.ts similarity index 72% rename from src/core/services/Jobs.ts rename to packages/gitbeaker-core/src/services/Jobs.ts index c0132f53..5f3a0405 100644 --- a/src/core/services/Jobs.ts +++ b/packages/gitbeaker-core/src/services/Jobs.ts @@ -5,6 +5,10 @@ import { RequestHelper, Sudo, } from '../infrastructure'; +import { CommitSchemaDefault, CommitSchemaCamelized } from './Commits'; +import { PipelineSchemaDefault, PipelineSchemaCamelized } from './Pipelines'; +import { RunnerSchemaDefault, RunnerSchemaCamelized } from './Runners'; +import { UserSchemaDefault, UserSchemaCamelized } from './Users'; export type JobScope = | 'created' @@ -16,6 +20,70 @@ export type JobScope = | 'skipped' | 'manual'; +// As of GitLab v12.6.2 +export type ArtifactSchema = ArtifactSchemaDefault | ArtifactSchemaCamelized; + +export interface ArtifactSchemaDefault { + file_type: string; + size: number; + filename: string; + file_format?: string; +} + +export interface ArtifactSchemaCamelized { + fileType: string; + size: number; + filename: string; + fileFormat?: string; +} + +// As of GitLab v12.6.2 +export type JobSchema = JobSchemaDefault | JobSchemaCamelized; + +export interface JobSchemaDefault { + id: number; + status: string; + stage: string; + name: string; + ref: string; + tag: boolean; + coverage?: string; + allow_failure: boolean; + created_at: Date; + started_at?: Date; + finished_at?: Date; + duration?: number; + user: UserSchemaDefault; + commit: CommitSchemaDefault; + pipeline: PipelineSchemaDefault; + web_url: string; + artifacts: ArtifactSchemaDefault[]; + runner: RunnerSchemaDefault; + artifacts_expire_at?: Date; +} + +export interface JobSchemaCamelized { + id: number; + status: string; + stage: string; + name: string; + ref: string; + tag: boolean; + coverage?: string; + allowFailure: boolean; + createdAt: Date; + startedAt?: Date; + finishedAt?: Date; + duration?: number; + user: UserSchemaCamelized; + commit: CommitSchemaCamelized; + pipeline: PipelineSchemaCamelized; + webUrl: string; + artifacts: ArtifactSchemaCamelized[]; + runner: RunnerSchemaCamelized; + artifactsExpireAt?: Date; +} + export class Jobs extends BaseService { all(projectId: string | number, options?: PaginatedRequestOptions) { const pId = encodeURIComponent(projectId); diff --git a/src/core/services/Keys.ts b/packages/gitbeaker-core/src/services/Keys.ts similarity index 100% rename from src/core/services/Keys.ts rename to packages/gitbeaker-core/src/services/Keys.ts diff --git a/src/core/services/Labels.ts b/packages/gitbeaker-core/src/services/Labels.ts similarity index 78% rename from src/core/services/Labels.ts rename to packages/gitbeaker-core/src/services/Labels.ts index 1aac0368..0bf77e75 100644 --- a/src/core/services/Labels.ts +++ b/packages/gitbeaker-core/src/services/Labels.ts @@ -2,7 +2,7 @@ import { BaseServiceOptions } from '../infrastructure'; import { ResourceLabels } from '../templates'; export class Labels extends ResourceLabels { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/LicenceTemplates.ts b/packages/gitbeaker-core/src/services/LicenceTemplates.ts similarity index 80% rename from src/core/services/LicenceTemplates.ts rename to packages/gitbeaker-core/src/services/LicenceTemplates.ts index 866a7133..773bd0d0 100644 --- a/src/core/services/LicenceTemplates.ts +++ b/packages/gitbeaker-core/src/services/LicenceTemplates.ts @@ -2,7 +2,7 @@ import { ResourceTemplates } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class LicenceTemplates extends ResourceTemplates { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('licences', options); } } diff --git a/src/core/services/Lint.ts b/packages/gitbeaker-core/src/services/Lint.ts similarity index 100% rename from src/core/services/Lint.ts rename to packages/gitbeaker-core/src/services/Lint.ts diff --git a/src/core/services/Markdown.ts b/packages/gitbeaker-core/src/services/Markdown.ts similarity index 100% rename from src/core/services/Markdown.ts rename to packages/gitbeaker-core/src/services/Markdown.ts diff --git a/src/core/services/MergeRequestAwardEmojis.ts b/packages/gitbeaker-core/src/services/MergeRequestAwardEmojis.ts similarity index 81% rename from src/core/services/MergeRequestAwardEmojis.ts rename to packages/gitbeaker-core/src/services/MergeRequestAwardEmojis.ts index 7b3282a6..b4f09971 100644 --- a/src/core/services/MergeRequestAwardEmojis.ts +++ b/packages/gitbeaker-core/src/services/MergeRequestAwardEmojis.ts @@ -2,7 +2,7 @@ import { ResourceAwardEmojis } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class MergeRequestAwardEmojis extends ResourceAwardEmojis { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('merge_requests', options); } } diff --git a/src/core/services/MergeRequestDiscussions.ts b/packages/gitbeaker-core/src/services/MergeRequestDiscussions.ts similarity index 82% rename from src/core/services/MergeRequestDiscussions.ts rename to packages/gitbeaker-core/src/services/MergeRequestDiscussions.ts index 6dac9933..54828e36 100644 --- a/src/core/services/MergeRequestDiscussions.ts +++ b/packages/gitbeaker-core/src/services/MergeRequestDiscussions.ts @@ -2,7 +2,7 @@ import { ResourceDiscussions } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class MergeRequestDiscussions extends ResourceDiscussions { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', 'merge_requests', options); } } diff --git a/src/core/services/MergeRequestNotes.ts b/packages/gitbeaker-core/src/services/MergeRequestNotes.ts similarity index 81% rename from src/core/services/MergeRequestNotes.ts rename to packages/gitbeaker-core/src/services/MergeRequestNotes.ts index e68780d9..2babd419 100644 --- a/src/core/services/MergeRequestNotes.ts +++ b/packages/gitbeaker-core/src/services/MergeRequestNotes.ts @@ -2,7 +2,7 @@ import { ResourceNotes } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class MergeRequestNotes extends ResourceNotes { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', 'merge_requests', options); } } diff --git a/src/core/services/MergeRequests.ts b/packages/gitbeaker-core/src/services/MergeRequests.ts similarity index 99% rename from src/core/services/MergeRequests.ts rename to packages/gitbeaker-core/src/services/MergeRequests.ts index 0d3e07cb..16072f78 100644 --- a/src/core/services/MergeRequests.ts +++ b/packages/gitbeaker-core/src/services/MergeRequests.ts @@ -141,7 +141,7 @@ export class MergeRequests extends BaseService { ) { const [pId, mIId] = [projectId, mergerequestIId].map(encodeURIComponent); - return RequestHelper.post( + return RequestHelper.get( this, `projects/${pId}/merge_requests/${mIId}/approval_state`, options, diff --git a/src/core/services/Namespaces.ts b/packages/gitbeaker-core/src/services/Namespaces.ts similarity index 100% rename from src/core/services/Namespaces.ts rename to packages/gitbeaker-core/src/services/Namespaces.ts diff --git a/src/core/services/Packages.ts b/packages/gitbeaker-core/src/services/Packages.ts similarity index 100% rename from src/core/services/Packages.ts rename to packages/gitbeaker-core/src/services/Packages.ts diff --git a/src/core/services/PagesDomains.ts b/packages/gitbeaker-core/src/services/PagesDomains.ts similarity index 100% rename from src/core/services/PagesDomains.ts rename to packages/gitbeaker-core/src/services/PagesDomains.ts diff --git a/src/core/services/PipelineScheduleVariables.ts b/packages/gitbeaker-core/src/services/PipelineScheduleVariables.ts similarity index 100% rename from src/core/services/PipelineScheduleVariables.ts rename to packages/gitbeaker-core/src/services/PipelineScheduleVariables.ts diff --git a/src/core/services/PipelineSchedules.ts b/packages/gitbeaker-core/src/services/PipelineSchedules.ts similarity index 100% rename from src/core/services/PipelineSchedules.ts rename to packages/gitbeaker-core/src/services/PipelineSchedules.ts diff --git a/src/core/services/Pipelines.ts b/packages/gitbeaker-core/src/services/Pipelines.ts similarity index 82% rename from src/core/services/Pipelines.ts rename to packages/gitbeaker-core/src/services/Pipelines.ts index ad48458c..64f93475 100644 --- a/src/core/services/Pipelines.ts +++ b/packages/gitbeaker-core/src/services/Pipelines.ts @@ -5,9 +5,31 @@ import { RequestHelper, Sudo, } from '../infrastructure'; - import { JobScope } from './Jobs'; +// As of GitLab v12.6.2 +export type PipelineSchema = PipelineSchemaDefault | PipelineSchemaCamelized; + +export interface PipelineSchemaDefault { + id: number; + sha: string; + ref: string; + status: string; + created_at: Date; + updated_at: Date; + web_url: string; +} + +export interface PipelineSchemaCamelized { + id: number; + sha: string; + ref: string; + status: string; + createdAt: Date; + updatedAt: Date; + webUrl: string; +} + export class Pipelines extends BaseService { all(projectId: string | number, options?: PaginatedRequestOptions) { const pId = encodeURIComponent(projectId); diff --git a/src/core/services/ProjectAccessRequests.ts b/packages/gitbeaker-core/src/services/ProjectAccessRequests.ts similarity index 81% rename from src/core/services/ProjectAccessRequests.ts rename to packages/gitbeaker-core/src/services/ProjectAccessRequests.ts index 3e54d556..2ffb368f 100644 --- a/src/core/services/ProjectAccessRequests.ts +++ b/packages/gitbeaker-core/src/services/ProjectAccessRequests.ts @@ -2,7 +2,7 @@ import { ResourceAccessRequests } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectAccessRequests extends ResourceAccessRequests { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/ProjectBadges.ts b/packages/gitbeaker-core/src/services/ProjectBadges.ts similarity index 79% rename from src/core/services/ProjectBadges.ts rename to packages/gitbeaker-core/src/services/ProjectBadges.ts index aa6a1e7a..9ca6f88d 100644 --- a/src/core/services/ProjectBadges.ts +++ b/packages/gitbeaker-core/src/services/ProjectBadges.ts @@ -2,7 +2,7 @@ import { ResourceBadges } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectBadges extends ResourceBadges { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/ProjectCustomAttributes.ts b/packages/gitbeaker-core/src/services/ProjectCustomAttributes.ts similarity index 81% rename from src/core/services/ProjectCustomAttributes.ts rename to packages/gitbeaker-core/src/services/ProjectCustomAttributes.ts index 401fc350..83d79463 100644 --- a/src/core/services/ProjectCustomAttributes.ts +++ b/packages/gitbeaker-core/src/services/ProjectCustomAttributes.ts @@ -2,7 +2,7 @@ import { ResourceCustomAttributes } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectCustomAttributes extends ResourceCustomAttributes { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/ProjectHooks.ts b/packages/gitbeaker-core/src/services/ProjectHooks.ts similarity index 100% rename from src/core/services/ProjectHooks.ts rename to packages/gitbeaker-core/src/services/ProjectHooks.ts diff --git a/src/core/services/ProjectImportExport.ts b/packages/gitbeaker-core/src/services/ProjectImportExport.ts similarity index 96% rename from src/core/services/ProjectImportExport.ts rename to packages/gitbeaker-core/src/services/ProjectImportExport.ts index b828de00..2a50d6ee 100644 --- a/src/core/services/ProjectImportExport.ts +++ b/packages/gitbeaker-core/src/services/ProjectImportExport.ts @@ -23,7 +23,7 @@ export class ProjectImportExport extends BaseService { const form = new FormData(); const defaultMetadata: UploadMetadata = { - filename: Date.now().toString(), + filename: `${Date.now().toString()}.tar.gz`, contentType: 'application/octet-stream', }; diff --git a/src/core/services/ProjectIssueBoards.ts b/packages/gitbeaker-core/src/services/ProjectIssueBoards.ts similarity index 80% rename from src/core/services/ProjectIssueBoards.ts rename to packages/gitbeaker-core/src/services/ProjectIssueBoards.ts index fe2ff02b..0cbbae57 100644 --- a/src/core/services/ProjectIssueBoards.ts +++ b/packages/gitbeaker-core/src/services/ProjectIssueBoards.ts @@ -2,7 +2,7 @@ import { ResourceIssueBoards } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectIssueBoards extends ResourceIssueBoards { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/ProjectMembers.ts b/packages/gitbeaker-core/src/services/ProjectMembers.ts similarity index 79% rename from src/core/services/ProjectMembers.ts rename to packages/gitbeaker-core/src/services/ProjectMembers.ts index 4228d1db..7cf26f90 100644 --- a/src/core/services/ProjectMembers.ts +++ b/packages/gitbeaker-core/src/services/ProjectMembers.ts @@ -2,7 +2,7 @@ import { ResourceMembers } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectMembers extends ResourceMembers { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/ProjectMilestones.ts b/packages/gitbeaker-core/src/services/ProjectMilestones.ts similarity index 80% rename from src/core/services/ProjectMilestones.ts rename to packages/gitbeaker-core/src/services/ProjectMilestones.ts index 42746714..6d9fb466 100644 --- a/src/core/services/ProjectMilestones.ts +++ b/packages/gitbeaker-core/src/services/ProjectMilestones.ts @@ -2,7 +2,7 @@ import { ResourceMilestones } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectMilestones extends ResourceMilestones { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/ProjectSnippetAwardEmojis.ts b/packages/gitbeaker-core/src/services/ProjectSnippetAwardEmojis.ts similarity index 81% rename from src/core/services/ProjectSnippetAwardEmojis.ts rename to packages/gitbeaker-core/src/services/ProjectSnippetAwardEmojis.ts index 6f3745c5..07b92a79 100644 --- a/src/core/services/ProjectSnippetAwardEmojis.ts +++ b/packages/gitbeaker-core/src/services/ProjectSnippetAwardEmojis.ts @@ -2,7 +2,7 @@ import { ResourceAwardEmojis } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectSnippetAwardEmojis extends ResourceAwardEmojis { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('issues', options); } } diff --git a/src/core/services/ProjectSnippetDiscussions.ts b/packages/gitbeaker-core/src/services/ProjectSnippetDiscussions.ts similarity index 82% rename from src/core/services/ProjectSnippetDiscussions.ts rename to packages/gitbeaker-core/src/services/ProjectSnippetDiscussions.ts index cd84e66c..890621d4 100644 --- a/src/core/services/ProjectSnippetDiscussions.ts +++ b/packages/gitbeaker-core/src/services/ProjectSnippetDiscussions.ts @@ -2,7 +2,7 @@ import { ResourceDiscussions } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectSnippetDiscussions extends ResourceDiscussions { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', 'snippets', options); } } diff --git a/src/core/services/ProjectSnippetNotes.ts b/packages/gitbeaker-core/src/services/ProjectSnippetNotes.ts similarity index 80% rename from src/core/services/ProjectSnippetNotes.ts rename to packages/gitbeaker-core/src/services/ProjectSnippetNotes.ts index 86ac3b35..afc796a5 100644 --- a/src/core/services/ProjectSnippetNotes.ts +++ b/packages/gitbeaker-core/src/services/ProjectSnippetNotes.ts @@ -2,7 +2,7 @@ import { ResourceNotes } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class ProjectSnippetNotes extends ResourceNotes { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', 'snippets', options); } } diff --git a/src/core/services/ProjectSnippets.ts b/packages/gitbeaker-core/src/services/ProjectSnippets.ts similarity index 100% rename from src/core/services/ProjectSnippets.ts rename to packages/gitbeaker-core/src/services/ProjectSnippets.ts diff --git a/src/core/services/ProjectVariables.ts b/packages/gitbeaker-core/src/services/ProjectVariables.ts similarity index 65% rename from src/core/services/ProjectVariables.ts rename to packages/gitbeaker-core/src/services/ProjectVariables.ts index 9f8e9aac..247705f9 100644 --- a/src/core/services/ProjectVariables.ts +++ b/packages/gitbeaker-core/src/services/ProjectVariables.ts @@ -1,8 +1,10 @@ import { ResourceVariables } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; +export { ResourceVariableSchema } from '../templates'; + export class ProjectVariables extends ResourceVariables { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('projects', options); } } diff --git a/src/core/services/Projects.ts b/packages/gitbeaker-core/src/services/Projects.ts similarity index 88% rename from src/core/services/Projects.ts rename to packages/gitbeaker-core/src/services/Projects.ts index bcea2ec1..12ea021e 100644 --- a/src/core/services/Projects.ts +++ b/packages/gitbeaker-core/src/services/Projects.ts @@ -9,7 +9,9 @@ import { import { EventOptions } from './Events'; import { UploadMetadata } from './ProjectImportExport'; -export interface NamespaceInfoSchema { +export type NamespaceInfoSchema = NamespaceInfoSchemaDefault | NamespaceInfoSchemaCamelize; + +export interface NamespaceInfoSchemaDefault { id: number; name: string; path: string; @@ -17,21 +19,40 @@ export interface NamespaceInfoSchema { full_path: string; } -export interface ProjectSchema { +export interface NamespaceInfoSchemaCamelize { id: number; + name: string; + path: string; + kind: string; + fullPath: string; +} +export type ProjectSchema = ProjectSchemaDefault | ProjectSchemaCamelized; + +export interface ProjectSchemaDefault { + id: number; name: string; name_with_namespace: string; path: string; path_with_namespace: string; - namespace: NamespaceInfoSchema; - ssh_url_to_repo: string; http_url_to_repo: string; archived: boolean; } +export interface ProjectSchemaCamelized { + id: number; + name: string; + nameWithNamespace: string; + path: string; + pathWithNamespace: string; + namespace: NamespaceInfoSchema; + sshUrlToRepo: string; + httpUrlToRepo: string; + archived: boolean; +} + export class Projects extends BaseService { all(options?: PaginatedRequestOptions) { return RequestHelper.get(this, 'projects', options); diff --git a/src/core/services/ProtectedBranches.ts b/packages/gitbeaker-core/src/services/ProtectedBranches.ts similarity index 100% rename from src/core/services/ProtectedBranches.ts rename to packages/gitbeaker-core/src/services/ProtectedBranches.ts diff --git a/src/core/services/ProtectedTags.ts b/packages/gitbeaker-core/src/services/ProtectedTags.ts similarity index 100% rename from src/core/services/ProtectedTags.ts rename to packages/gitbeaker-core/src/services/ProtectedTags.ts diff --git a/src/core/services/PushRules.ts b/packages/gitbeaker-core/src/services/PushRules.ts similarity index 100% rename from src/core/services/PushRules.ts rename to packages/gitbeaker-core/src/services/PushRules.ts diff --git a/src/core/services/ReleaseLinks.ts b/packages/gitbeaker-core/src/services/ReleaseLinks.ts similarity index 100% rename from src/core/services/ReleaseLinks.ts rename to packages/gitbeaker-core/src/services/ReleaseLinks.ts diff --git a/src/core/services/Releases.ts b/packages/gitbeaker-core/src/services/Releases.ts similarity index 100% rename from src/core/services/Releases.ts rename to packages/gitbeaker-core/src/services/Releases.ts diff --git a/src/core/services/Repositories.ts b/packages/gitbeaker-core/src/services/Repositories.ts similarity index 100% rename from src/core/services/Repositories.ts rename to packages/gitbeaker-core/src/services/Repositories.ts diff --git a/src/core/services/RepositoryFiles.ts b/packages/gitbeaker-core/src/services/RepositoryFiles.ts similarity index 86% rename from src/core/services/RepositoryFiles.ts rename to packages/gitbeaker-core/src/services/RepositoryFiles.ts index b54bbd84..e4fc769a 100644 --- a/src/core/services/RepositoryFiles.ts +++ b/packages/gitbeaker-core/src/services/RepositoryFiles.ts @@ -1,6 +1,8 @@ import { BaseService, RequestHelper, BaseRequestOptions, Sudo } from '../infrastructure'; -export interface RepositoryFileSchema { +export type RepositoryFileSchema = RepositoryFileSchemaDefault | RepositoryFileSchemaCamelized; + +export interface RepositoryFileSchemaDefault { file_name: string; file_path: string; size: number; @@ -13,6 +15,19 @@ export interface RepositoryFileSchema { last_commit_id: string; } +export interface RepositoryFileSchemaCamelized { + fileName: string; + filePath: string; + size: number; + encoding: string; + content: string; + contentSha256: string; + ref: string; + blobId: string; + commitId: string; + lastCommitId: string; +} + export class RepositoryFiles extends BaseService { create( projectId: string | number, diff --git a/src/core/services/Runners.ts b/packages/gitbeaker-core/src/services/Runners.ts similarity index 77% rename from src/core/services/Runners.ts rename to packages/gitbeaker-core/src/services/Runners.ts index 8bca9f73..2b9b005f 100644 --- a/src/core/services/Runners.ts +++ b/packages/gitbeaker-core/src/services/Runners.ts @@ -6,6 +6,31 @@ import { Sudo, } from '../infrastructure'; +// As of GitLab v12.6.2 +export type RunnerSchema = RunnerSchemaDefault | RunnerSchemaCamelized; + +export interface RunnerSchemaDefault { + id: number; + description: string; + ip_address: string; + active: boolean; + is_shared: boolean; + name: string; + online: boolean; + status: string; +} + +export interface RunnerSchemaCamelized { + id: number; + description: string; + ipAddress: string; + active: boolean; + isShared: boolean; + name: string; + online: boolean; + status: string; +} + export class Runners extends BaseService { all({ projectId, ...options }: { projectId: string | number } & PaginatedRequestOptions) { const url = projectId ? `projects/${encodeURIComponent(projectId)}/runners` : 'runners/all'; diff --git a/src/core/services/Search.ts b/packages/gitbeaker-core/src/services/Search.ts similarity index 100% rename from src/core/services/Search.ts rename to packages/gitbeaker-core/src/services/Search.ts diff --git a/src/core/services/Services.ts b/packages/gitbeaker-core/src/services/Services.ts similarity index 100% rename from src/core/services/Services.ts rename to packages/gitbeaker-core/src/services/Services.ts diff --git a/src/core/services/SidekiqMetrics.ts b/packages/gitbeaker-core/src/services/SidekiqMetrics.ts similarity index 100% rename from src/core/services/SidekiqMetrics.ts rename to packages/gitbeaker-core/src/services/SidekiqMetrics.ts diff --git a/src/core/services/Snippets.ts b/packages/gitbeaker-core/src/services/Snippets.ts similarity index 100% rename from src/core/services/Snippets.ts rename to packages/gitbeaker-core/src/services/Snippets.ts diff --git a/src/core/services/SystemHooks.ts b/packages/gitbeaker-core/src/services/SystemHooks.ts similarity index 100% rename from src/core/services/SystemHooks.ts rename to packages/gitbeaker-core/src/services/SystemHooks.ts diff --git a/src/core/services/Tags.ts b/packages/gitbeaker-core/src/services/Tags.ts similarity index 100% rename from src/core/services/Tags.ts rename to packages/gitbeaker-core/src/services/Tags.ts diff --git a/src/core/services/Todos.ts b/packages/gitbeaker-core/src/services/Todos.ts similarity index 100% rename from src/core/services/Todos.ts rename to packages/gitbeaker-core/src/services/Todos.ts diff --git a/src/core/services/Triggers.ts b/packages/gitbeaker-core/src/services/Triggers.ts similarity index 100% rename from src/core/services/Triggers.ts rename to packages/gitbeaker-core/src/services/Triggers.ts diff --git a/src/core/services/UserCustomAttributes.ts b/packages/gitbeaker-core/src/services/UserCustomAttributes.ts similarity index 81% rename from src/core/services/UserCustomAttributes.ts rename to packages/gitbeaker-core/src/services/UserCustomAttributes.ts index 79922944..4a5c1045 100644 --- a/src/core/services/UserCustomAttributes.ts +++ b/packages/gitbeaker-core/src/services/UserCustomAttributes.ts @@ -2,7 +2,7 @@ import { ResourceCustomAttributes } from '../templates'; import { BaseServiceOptions } from '../infrastructure'; export class UserCustomAttributes extends ResourceCustomAttributes { - constructor(options: BaseServiceOptions) { + constructor(options: BaseServiceOptions = {}) { super('users', options); } } diff --git a/src/core/services/UserEmails.ts b/packages/gitbeaker-core/src/services/UserEmails.ts similarity index 100% rename from src/core/services/UserEmails.ts rename to packages/gitbeaker-core/src/services/UserEmails.ts diff --git a/src/core/services/UserGPGKeys.ts b/packages/gitbeaker-core/src/services/UserGPGKeys.ts similarity index 100% rename from src/core/services/UserGPGKeys.ts rename to packages/gitbeaker-core/src/services/UserGPGKeys.ts diff --git a/src/core/services/UserImpersonationTokens.ts b/packages/gitbeaker-core/src/services/UserImpersonationTokens.ts similarity index 100% rename from src/core/services/UserImpersonationTokens.ts rename to packages/gitbeaker-core/src/services/UserImpersonationTokens.ts diff --git a/src/core/services/UserKeys.ts b/packages/gitbeaker-core/src/services/UserKeys.ts similarity index 100% rename from src/core/services/UserKeys.ts rename to packages/gitbeaker-core/src/services/UserKeys.ts diff --git a/src/core/services/Users.ts b/packages/gitbeaker-core/src/services/Users.ts similarity index 65% rename from src/core/services/Users.ts rename to packages/gitbeaker-core/src/services/Users.ts index 6e050c0b..f4c6380a 100644 --- a/src/core/services/Users.ts +++ b/packages/gitbeaker-core/src/services/Users.ts @@ -8,6 +8,54 @@ import { import { EventOptions } from './Events'; +// As of GitLab v12.6.2 +export type UserSchema = UserSchemaDefault | UserSchemaCamelized; + +export interface UserSchemaDefault { + id: number; + name: string; + username: string; + state: string; + avatar_url: string; + web_url: string; +} + +export interface UserSchemaCamelized { + id: number; + name: string; + username: string; + state: string; + avatarUrl: string; + webUrl: string; +} + +// As of GitLab v12.6.2 +export type UserDetailSchema = UserDetailSchemaDefault | UserSchemaCamelized; + +export interface UserDetailSchemaDefault extends UserSchemaDefault { + created_at: Date; + bio?: string; + location?: string; + public_email: string; + skype: string; + linkedin: string; + twitter: string; + website_url?: string; + organization?: string; +} + +export interface UserDetailSchemaCamelized extends UserSchemaCamelized { + createdAt: Date; + bio?: string; + location?: string; + publicEmail: string; + skype: string; + linkedin: string; + twitter: string; + websiteUrl?: string; + organization?: string; +} + export class Users extends BaseService { all(options?: PaginatedRequestOptions) { return RequestHelper.get(this, 'users', options); diff --git a/src/core/services/Version.ts b/packages/gitbeaker-core/src/services/Version.ts similarity index 100% rename from src/core/services/Version.ts rename to packages/gitbeaker-core/src/services/Version.ts diff --git a/packages/gitbeaker-core/src/services/VulnerabilityFindings.ts b/packages/gitbeaker-core/src/services/VulnerabilityFindings.ts new file mode 100644 index 00000000..ea9e4f89 --- /dev/null +++ b/packages/gitbeaker-core/src/services/VulnerabilityFindings.ts @@ -0,0 +1,19 @@ +import { BaseService, PaginatedRequestOptions, RequestHelper } from '../infrastructure'; + +export class VulnerabilityFindings extends BaseService { + all( + projectId: string | number, + options?: { + id: string | number; + reportType: string[]; + scope: string; + severity: string[]; + confidence: string[]; + pipelineId: string | number; + } & PaginatedRequestOptions, + ) { + const pId = encodeURIComponent(projectId); + + return RequestHelper.get(this, `projects/${pId}/vulnerability_findings`, options); + } +} diff --git a/src/core/services/Wikis.ts b/packages/gitbeaker-core/src/services/Wikis.ts similarity index 100% rename from src/core/services/Wikis.ts rename to packages/gitbeaker-core/src/services/Wikis.ts diff --git a/src/core/services/index.ts b/packages/gitbeaker-core/src/services/index.ts similarity index 95% rename from src/core/services/index.ts rename to packages/gitbeaker-core/src/services/index.ts index a6636f49..094437ce 100644 --- a/src/core/services/index.ts +++ b/packages/gitbeaker-core/src/services/index.ts @@ -1,5 +1,5 @@ // Groups -export * from './Groups'; +// export * from './Groups'; export * from './GroupAccessRequests'; export * from './GroupBadges'; export * from './GroupCustomAttributes'; @@ -71,6 +71,7 @@ export * from './Services'; export * from './Tags'; export * from './Todos'; export * from './Triggers'; +export * from './VulnerabilityFindings'; // General export * from './ApplicationSettings'; @@ -81,11 +82,11 @@ export * from './GeoNodes'; export * from './GitignoreTemplates'; export * from './GitLabCIYMLTemplates'; export * from './Keys'; -export * from './Licence'; +// export * from './License'; export * from './LicenceTemplates'; export * from './Lint'; export * from './Namespaces'; -export * from './NotificationSettings'; +// export * from './NotificationSettings'; export * from './Markdown'; export * from './PagesDomains'; export * from './Search'; diff --git a/src/core/templates/ResourceAccessRequests.ts b/packages/gitbeaker-core/src/templates/ResourceAccessRequests.ts similarity index 90% rename from src/core/templates/ResourceAccessRequests.ts rename to packages/gitbeaker-core/src/templates/ResourceAccessRequests.ts index 71c3c49c..a29a5c37 100644 --- a/src/core/templates/ResourceAccessRequests.ts +++ b/packages/gitbeaker-core/src/templates/ResourceAccessRequests.ts @@ -22,13 +22,11 @@ export class ResourceAccessRequests extends BaseService { approve( resourceId: string | number, userId: number, - { accessLevel }: { accessLevel?: AccessLevel } & Sudo = {}, + options?: { accessLevel?: AccessLevel } & Sudo, ) { const [rId, uId] = [resourceId, userId].map(encodeURIComponent); - return RequestHelper.post(this, `${rId}/access_requests/${uId}/approve`, { - accessLevel, - }); + return RequestHelper.post(this, `${rId}/access_requests/${uId}/approve`, options); } deny(resourceId: string | number, userId: number) { diff --git a/src/core/templates/ResourceBadges.ts b/packages/gitbeaker-core/src/templates/ResourceBadges.ts similarity index 100% rename from src/core/templates/ResourceBadges.ts rename to packages/gitbeaker-core/src/templates/ResourceBadges.ts diff --git a/src/core/templates/ResourceCustomAttributes.ts b/packages/gitbeaker-core/src/templates/ResourceCustomAttributes.ts similarity index 100% rename from src/core/templates/ResourceCustomAttributes.ts rename to packages/gitbeaker-core/src/templates/ResourceCustomAttributes.ts diff --git a/src/core/templates/ResourceDiscussions.ts b/packages/gitbeaker-core/src/templates/ResourceDiscussions.ts similarity index 90% rename from src/core/templates/ResourceDiscussions.ts rename to packages/gitbeaker-core/src/templates/ResourceDiscussions.ts index 945c539a..316008f9 100644 --- a/src/core/templates/ResourceDiscussions.ts +++ b/packages/gitbeaker-core/src/templates/ResourceDiscussions.ts @@ -19,13 +19,11 @@ export class ResourceDiscussions extends BaseService { addNote( resourceId: string | number, resource2Id: string | number, - discussionId: number, + discussionId: string | number, noteId: number, content: string, options?: BaseRequestOptions, ) { - if (!content) throw new Error('Missing required content argument'); - const [rId, r2Id, dId, nId] = [resourceId, resource2Id, discussionId, noteId].map( encodeURIComponent, ); @@ -53,8 +51,6 @@ export class ResourceDiscussions extends BaseService { content: string, options?: BaseRequestOptions, ) { - if (!content) throw new Error('Missing required content argument'); - const [rId, r2Id] = [resourceId, resource2Id].map(encodeURIComponent); return RequestHelper.post(this, `${rId}/${this.resource2Type}/${r2Id}/discussions`, { @@ -66,8 +62,9 @@ export class ResourceDiscussions extends BaseService { editNote( resourceId: string | number, resource2Id: string | number, - discussionId: number, + discussionId: string | number, noteId: number, + content: string, options?: BaseRequestOptions, ) { const [rId, r2Id, dId, nId] = [resourceId, resource2Id, discussionId, noteId].map( @@ -77,14 +74,17 @@ export class ResourceDiscussions extends BaseService { return RequestHelper.put( this, `${rId}/${this.resource2Type}/${r2Id}/discussions/${dId}/notes/${nId}`, - { body: options }, + { + body: content, + ...options, + }, ); } removeNote( resourceId: string | number, resource2Id: string | number, - discussionId: number, + discussionId: string | number, noteId: number, options?: Sudo, ) { @@ -102,7 +102,7 @@ export class ResourceDiscussions extends BaseService { show( resourceId: string | number, resource2Id: string | number, - discussionId: number, + discussionId: string | number, options?: Sudo, ) { const [rId, r2Id, dId] = [resourceId, resource2Id, discussionId].map(encodeURIComponent); diff --git a/src/core/templates/ResourceIssueBoards.ts b/packages/gitbeaker-core/src/templates/ResourceIssueBoards.ts similarity index 100% rename from src/core/templates/ResourceIssueBoards.ts rename to packages/gitbeaker-core/src/templates/ResourceIssueBoards.ts diff --git a/src/core/templates/ResourceLabels.ts b/packages/gitbeaker-core/src/templates/ResourceLabels.ts similarity index 79% rename from src/core/templates/ResourceLabels.ts rename to packages/gitbeaker-core/src/templates/ResourceLabels.ts index a50186d6..cf0944ea 100644 --- a/src/core/templates/ResourceLabels.ts +++ b/packages/gitbeaker-core/src/templates/ResourceLabels.ts @@ -5,14 +5,15 @@ import { PaginatedRequestOptions, RequestHelper, Sudo, + ShowExpanded, } from '../infrastructure'; export class ResourceLabels extends BaseService { - constructor(resourceType: string, options: BaseServiceOptions) { + constructor(resourceType: string, options: BaseServiceOptions & ShowExpanded) { super({ url: resourceType, ...options }); } - all(resourceId: string | number, options?: PaginatedRequestOptions) { + all(resourceId: string | number, options?: PaginatedRequestOptions & ShowExpanded) { const rId = encodeURIComponent(resourceId); return RequestHelper.get(this, `${rId}/labels`, options); @@ -22,7 +23,7 @@ export class ResourceLabels extends BaseService { resourceId: string | number, labelName: string, color: string, - options?: BaseRequestOptions, + options?: BaseRequestOptions & ShowExpanded, ) { const rId = encodeURIComponent(resourceId); @@ -33,25 +34,29 @@ export class ResourceLabels extends BaseService { }); } - edit(resourceId: string | number, labelName: string, options?: BaseRequestOptions) { + edit( + resourceId: string | number, + labelName: string, + options?: BaseRequestOptions & ShowExpanded, + ) { const rId = encodeURIComponent(resourceId); return RequestHelper.put(this, `${rId}/labels`, { name: labelName, ...options }); } - remove(resourceId: string | number, labelName: string, options?: Sudo) { + remove(resourceId: string | number, labelName: string, options?: Sudo & ShowExpanded) { const rId = encodeURIComponent(resourceId); return RequestHelper.del(this, `${rId}/labels`, { name: labelName, ...options }); } - subscribe(resourceId: string | number, labelId: number, options?: Sudo) { + subscribe(resourceId: string | number, labelId: number, options?: Sudo & ShowExpanded) { const [rId, lId] = [resourceId, labelId].map(encodeURIComponent); return RequestHelper.post(this, `${rId}/issues/${lId}/subscribe`, options); } - unsubscribe(resourceId: string | number, labelId: number, options?: Sudo) { + unsubscribe(resourceId: string | number, labelId: number, options?: Sudo & ShowExpanded) { const [rId, lId] = [resourceId, labelId].map(encodeURIComponent); return RequestHelper.del(this, `${rId}/issues/${lId}/unsubscribe`, options); diff --git a/src/core/templates/ResourceMembers.ts b/packages/gitbeaker-core/src/templates/ResourceMembers.ts similarity index 93% rename from src/core/templates/ResourceMembers.ts rename to packages/gitbeaker-core/src/templates/ResourceMembers.ts index b23a0547..c9f3ecca 100644 --- a/src/core/templates/ResourceMembers.ts +++ b/packages/gitbeaker-core/src/templates/ResourceMembers.ts @@ -17,18 +17,6 @@ export class ResourceMembers extends BaseService { super({ url: resourceType, ...options }); } - all( - resourceId: string | number, - { includeInherited, ...options }: IncludeInherited & PaginatedRequestOptions = {}, - ) { - const rId = encodeURIComponent(resourceId); - const url = [rId, 'members']; - - if (includeInherited) url.push('all'); - - return RequestHelper.get(this, url.join('/'), { options }); - } - add( resourceId: string | number, userId: number, @@ -44,6 +32,18 @@ export class ResourceMembers extends BaseService { }); } + all( + resourceId: string | number, + { includeInherited, ...options }: IncludeInherited & PaginatedRequestOptions = {}, + ) { + const rId = encodeURIComponent(resourceId); + const url = [rId, 'members']; + + if (includeInherited) url.push('all'); + + return RequestHelper.get(this, url.join('/'), options); + } + edit( resourceId: string | number, userId: number, @@ -70,7 +70,7 @@ export class ResourceMembers extends BaseService { url.push(uId); - return RequestHelper.get(this, url.join('/'), { options }); + return RequestHelper.get(this, url.join('/'), options); } remove(resourceId: string | number, userId: number, options?: Sudo) { diff --git a/src/core/templates/ResourceMilestones.ts b/packages/gitbeaker-core/src/templates/ResourceMilestones.ts similarity index 100% rename from src/core/templates/ResourceMilestones.ts rename to packages/gitbeaker-core/src/templates/ResourceMilestones.ts diff --git a/src/core/templates/ResourceNotes.ts b/packages/gitbeaker-core/src/templates/ResourceNotes.ts similarity index 100% rename from src/core/templates/ResourceNotes.ts rename to packages/gitbeaker-core/src/templates/ResourceNotes.ts diff --git a/src/core/templates/ResourceVariables.ts b/packages/gitbeaker-core/src/templates/ResourceVariables.ts similarity index 68% rename from src/core/templates/ResourceVariables.ts rename to packages/gitbeaker-core/src/templates/ResourceVariables.ts index 532146dd..ce19b429 100644 --- a/src/core/templates/ResourceVariables.ts +++ b/packages/gitbeaker-core/src/templates/ResourceVariables.ts @@ -6,15 +6,26 @@ import { BaseRequestOptions, } from '../infrastructure'; +export interface ResourceVariableSchema { + key: string; + variable_type: 'env_var' | 'file'; + value: string; +} + export class ResourceVariables extends BaseService { constructor(resourceType: string, options: BaseServiceOptions) { super({ url: resourceType, ...options }); } - all(resourceId: string | number, options?: PaginatedRequestOptions) { + all( + resourceId: string | number, + options?: PaginatedRequestOptions, + ): Promise { const rId = encodeURIComponent(resourceId); - return RequestHelper.get(this, `${rId}/variables`, options); + return RequestHelper.get(this, `${rId}/variables`, options) as Promise< + ResourceVariableSchema[] + >; } create(resourceId: string | number, options?: BaseRequestOptions) { @@ -29,10 +40,16 @@ export class ResourceVariables extends BaseService { return RequestHelper.put(this, `${rId}/variables/${kId}`, options); } - show(resourceId: string | number, keyId: string, options?: PaginatedRequestOptions) { + show( + resourceId: string | number, + keyId: string, + options?: PaginatedRequestOptions, + ): Promise { const [rId, kId] = [resourceId, keyId].map(encodeURIComponent); - return RequestHelper.get(this, `${rId}/variables/${kId}`, options); + return RequestHelper.get(this, `${rId}/variables/${kId}`, options) as Promise< + ResourceVariableSchema + >; } remove(resourceId: string | number, keyId: string, options?: PaginatedRequestOptions) { diff --git a/src/core/templates/index.ts b/packages/gitbeaker-core/src/templates/index.ts similarity index 82% rename from src/core/templates/index.ts rename to packages/gitbeaker-core/src/templates/index.ts index 83f9f80e..28470f30 100644 --- a/src/core/templates/index.ts +++ b/packages/gitbeaker-core/src/templates/index.ts @@ -1,5 +1,5 @@ export * from './ResourceAccessRequests'; -export * from './ResourceAwardEmojis'; +// export * from './ResourceAwardEmojis'; export * from './ResourceBadges'; export * from './ResourceCustomAttributes'; export * from './ResourceDiscussions'; @@ -8,5 +8,5 @@ export * from './ResourceLabels'; export * from './ResourceMembers'; export * from './ResourceMilestones'; export * from './ResourceNotes'; -export * from './ResourceTemplates'; +// export * from './ResourceTemplates'; export * from './ResourceVariables'; diff --git a/test/unit/bundles/Gitlab.ts b/packages/gitbeaker-core/test/unit/bundles/Gitlab.ts similarity index 71% rename from test/unit/bundles/Gitlab.ts rename to packages/gitbeaker-core/test/unit/bundles/Gitlab.ts index 8dd46e3f..8a42b817 100644 --- a/test/unit/bundles/Gitlab.ts +++ b/packages/gitbeaker-core/test/unit/bundles/Gitlab.ts @@ -1,9 +1,10 @@ -import { Gitlab } from '../../../src/core'; -import * as Services from '../../../src/core/services'; +import { Gitlab } from '../../../src'; +import * as Services from '../../../src/services'; describe('Instantiating All services', () => { it('should create a valid gitlab service object using import', async () => { const bundle = new Gitlab({ + requester: {}, token: 'abcdefg', }); diff --git a/test/unit/bundles/GroupsBundle.ts b/packages/gitbeaker-core/test/unit/bundles/GroupsBundle.ts similarity index 73% rename from test/unit/bundles/GroupsBundle.ts rename to packages/gitbeaker-core/test/unit/bundles/GroupsBundle.ts index 6032eacb..ba11f089 100644 --- a/test/unit/bundles/GroupsBundle.ts +++ b/packages/gitbeaker-core/test/unit/bundles/GroupsBundle.ts @@ -1,8 +1,8 @@ -import { GroupsBundle } from '../../../src/core'; -import * as Services from '../../../src/core/services'; +import { GroupsBundle } from '../../../src'; +import * as Services from '../../../src/services'; test('All the correct service keys are included in the groups bundle', async () => { - const bundle: GroupsBundle = new GroupsBundle({ token: 'test' }); + const bundle: GroupsBundle = new GroupsBundle({ requester: {}, token: 'test' }); const services = [ 'Groups', 'GroupAccessRequests', @@ -24,7 +24,7 @@ test('All the correct service keys are included in the groups bundle', async () }); test('All the correct service instances are included in the groups bundle', async () => { - const bundle = new GroupsBundle({ token: 'test' }); + const bundle = new GroupsBundle({ requester: {}, token: 'test' }); (Object.keys(bundle) as (keyof typeof bundle)[]).forEach(key => { expect(bundle[key]).toBeInstanceOf(Services[key]); diff --git a/test/unit/bundles/ProjectsBundle.ts b/packages/gitbeaker-core/test/unit/bundles/ProjectsBundle.ts similarity index 83% rename from test/unit/bundles/ProjectsBundle.ts rename to packages/gitbeaker-core/test/unit/bundles/ProjectsBundle.ts index 6cd2f9c8..32a81bba 100644 --- a/test/unit/bundles/ProjectsBundle.ts +++ b/packages/gitbeaker-core/test/unit/bundles/ProjectsBundle.ts @@ -1,8 +1,8 @@ -import { ProjectsBundle } from '../../../src/core'; -import * as Services from '../../../src/core/services'; +import { ProjectsBundle } from '../../../src'; +import * as Services from '../../../src/services'; test('All the correct service keys are included in the projects bundle', async () => { - const bundle: ProjectsBundle = new ProjectsBundle({ token: 'test' }); + const bundle: ProjectsBundle = new ProjectsBundle({ requester: {}, token: 'test' }); const services = [ 'Branches', 'Commits', @@ -50,7 +50,7 @@ test('All the correct service keys are included in the projects bundle', async ( }); test('All the correct service instances are included in the projects bundle', async () => { - const bundle = new ProjectsBundle({ token: 'test' }); + const bundle = new ProjectsBundle({ requester: {}, token: 'test' }); (Object.keys(bundle) as (keyof typeof bundle)[]).forEach(key => { expect(bundle[key]).toBeInstanceOf(Services[key]); diff --git a/test/unit/bundles/UsersBundle.ts b/packages/gitbeaker-core/test/unit/bundles/UsersBundle.ts similarity index 68% rename from test/unit/bundles/UsersBundle.ts rename to packages/gitbeaker-core/test/unit/bundles/UsersBundle.ts index cd8dd778..76299c1e 100644 --- a/test/unit/bundles/UsersBundle.ts +++ b/packages/gitbeaker-core/test/unit/bundles/UsersBundle.ts @@ -1,8 +1,8 @@ -import { UsersBundle } from '../../../src/core'; -import * as Services from '../../../src/core/services'; +import { UsersBundle } from '../../../src'; +import * as Services from '../../../src/services'; test('All the correct service keys are included in the users bundle', async () => { - const bundle: UsersBundle = new UsersBundle({ token: 'test' }); + const bundle: UsersBundle = new UsersBundle({ requester: {}, token: 'test' }); const services = [ 'Users', 'UserEmails', @@ -16,7 +16,7 @@ test('All the correct service keys are included in the users bundle', async () = }); test('All the correct service instances are included in the users bundle', async () => { - const bundle = new UsersBundle({ token: 'test' }); + const bundle = new UsersBundle({ requester: {}, token: 'test' }); (Object.keys(bundle) as (keyof typeof bundle)[]).forEach(key => { expect(bundle[key]).toBeInstanceOf(Services[key]); diff --git a/packages/gitbeaker-core/test/unit/infrastructure/BaseService.ts b/packages/gitbeaker-core/test/unit/infrastructure/BaseService.ts new file mode 100644 index 00000000..4cc65404 --- /dev/null +++ b/packages/gitbeaker-core/test/unit/infrastructure/BaseService.ts @@ -0,0 +1,149 @@ +import { RequestHelper, BaseService } from '../../../src/infrastructure'; + +describe('Creation of BaseService instance', () => { + it('should default host to https://gitlab.com/api/v4/', async () => { + const service = new BaseService({ requester: {}, token: 'test' }); + + expect(service.url).toBe('https://gitlab.com/api/v4/'); + }); + + it('should use the Oauth Token when a given both a Private Token and a Oauth Token', async () => { + const service = new BaseService({ requester: {}, token: 'test', oauthToken: '1234' }); + + expect(service.headers['private-token']).toBeUndefined(); + expect(service.headers.authorization).toBe('Bearer 1234'); + }); + + it('should append api and version number to host when using a custom host url', async () => { + const service = new BaseService({ requester: {}, host: 'https://testing.com', token: 'test' }); + + expect(service.url).toBe('https://testing.com/api/v4/'); + }); + + it('should add Oauth token to authorization header as a bearer token', async () => { + const service = new BaseService({ + requester: {}, + host: 'https://testing.com', + oauthToken: '1234', + }); + + expect(service.headers.authorization).toBe('Bearer 1234'); + }); + + it('should add Private token to private-token header', async () => { + const service = new BaseService({ requester: {}, host: 'https://testing.com', token: '1234' }); + + expect(service.headers['private-token']).toBe('1234'); + }); + + it('should add Job token to job-token header', async () => { + const service = new BaseService({ + requester: {}, + host: 'https://testing.com', + jobToken: '1234', + }); + + expect(service.headers['job-token']).toBe('1234'); + }); + + it('should allow for the API version to be modified', async () => { + const service = new BaseService({ + requester: {}, + host: 'https://testing.com', + token: '1234', + version: 3, + }); + + expect(service.url).toBe('https://testing.com/api/v3/'); + }); + + /* eslint @typescript-eslint/camelcase: 0 */ + it('should return simple response with camelized keys when using the camelize option', async () => { + const requester = {} as any; + const service = new BaseService({ + requester, + host: 'https://testing.com', + token: '1234', + camelize: true, + }); + + service.show = jest.fn(() => RequestHelper.get(service, 'test')); + requester.get = jest.fn(() => ({ + body: [ + { id: 3, gravatar_enable: true }, + { id: 4, gravatar_enable: false }, + ], + headers: {}, + })); + + const results = await service.show(); + + expect(results).toIncludeSameMembers([ + { id: 3, gravatarEnable: true }, + { id: 4, gravatarEnable: false }, + ]); + }); + + /* eslint @typescript-eslint/camelcase: 0 */ + it('should return simple response with default keys without camelize option', async () => { + const requester = {} as any; + const service = new BaseService({ requester, host: 'https://testing.com', token: '1234' }); + + service.show = jest.fn(() => RequestHelper.get(service, 'test')); + requester.get = jest.fn(() => ({ body: { id: 3, gravatar_enable: true }, headers: {} })); + + const results = await service.show(); + + expect(results).toMatchObject({ id: 3, gravatar_enable: true }); + }); + + it('should set the X-Profile-Token header if the profileToken option is given', async () => { + const service = new BaseService({ requester: {}, profileToken: 'abcd' }); + + expect(service.headers['X-Profile-Token']).toBe('abcd'); + }); + + it('should defult the profileMode option to execution', async () => { + const service = new BaseService({ requester: {}, profileToken: 'abcd' }); + + expect(service.headers['X-Profile-Token']).toBe('abcd'); + expect(service.headers['X-Profile-Mode']).toBe('execution'); + }); + + it('should set the X-Profile-Token and X-Profile-Mode header if the profileToken and profileMode options are given', async () => { + const service = new BaseService({ requester: {}, profileToken: 'abcd', profileMode: 'memory' }); + + expect(service.headers['X-Profile-Token']).toBe('abcd'); + expect(service.headers['X-Profile-Mode']).toBe('memory'); + }); + + it('should default the https reject unauthorized option to true', async () => { + const service = new BaseService({ requester: {}, rejectUnauthorized: true }); + + expect(service.rejectUnauthorized).toBeTruthy(); + }); + + it('should allow for the https reject unauthorized option to be set', async () => { + const service = new BaseService({ requester: {}, rejectUnauthorized: false }); + + expect(service.rejectUnauthorized).toBeFalsy(); + }); + + it('should default the requestTimeout to 300s', async () => { + const service = new BaseService({ requester: {} }); + + expect(service.requestTimeout).toBe(300000); + }); + + it('should allow for the requestTimeout to be set', async () => { + const service = new BaseService({ requester: {}, requestTimeout: 10 }); + + expect(service.requestTimeout).toBe(10); + }); + + it('should allow for the sudo user to be set', async () => { + const service = new BaseService({ requester: {}, sudo: 'test' }); + + expect(service.headers.Sudo).toBe('test'); + }); +}); diff --git a/test/unit/infrastructure/RequestHelper.ts b/packages/gitbeaker-core/test/unit/infrastructure/RequestHelper.ts similarity index 54% rename from test/unit/infrastructure/RequestHelper.ts rename to packages/gitbeaker-core/test/unit/infrastructure/RequestHelper.ts index 580932aa..1178447c 100644 --- a/test/unit/infrastructure/RequestHelper.ts +++ b/packages/gitbeaker-core/test/unit/infrastructure/RequestHelper.ts @@ -1,4 +1,4 @@ -import { RequestHelper, KyRequester, BaseService } from '../../../src/core/infrastructure'; +import { RequestHelper, BaseService } from '../../../src/infrastructure'; /* eslint no-empty-pattern: 0 */ /* eslint prefer-destructuring: 0 */ @@ -66,25 +66,32 @@ const mockedGetExtended = (url, { query }, limit = 30) => { return pages[page - 1]; }; -const service = new BaseService({ - host: 'https://testing.com', - token: 'token', +let requester; +let service; + +beforeEach(() => { + requester = {}; + service = new BaseService({ + requester, + host: 'https://testing.com', + token: 'token', + }); }); describe('RequestHelper.get()', () => { - it('Should respond with the proper get url without pagination', async () => { - KyRequester.get = jest.fn(() => mockedGetBasic()); + it('should respond with the proper get url without pagination', async () => { + requester.get = jest.fn(() => mockedGetBasic()); await RequestHelper.get(service, 'test'); - expect(KyRequester.get).toHaveBeenCalledWith(service, 'test', { + expect(requester.get).toHaveBeenCalledWith(service, 'test', { query: {}, sudo: undefined, }); }); - it('Should respond with an object', async () => { - KyRequester.get = jest.fn(() => mockedGetBasic()); + it('should respond with an object', async () => { + requester.get = jest.fn(() => mockedGetBasic()); const response = await RequestHelper.get(service, 'test'); @@ -92,8 +99,8 @@ describe('RequestHelper.get()', () => { expect(response.prop2).toBe('test property'); }); - it('Should be paginated when links are present', async () => { - KyRequester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); + it('should be paginated when links are present', async () => { + requester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); const response = await RequestHelper.get(service, 'test'); @@ -105,8 +112,8 @@ describe('RequestHelper.get()', () => { expect(response).toHaveLength(60); }); - it('Should handle large paginated (50 pages) results when links are present', async () => { - KyRequester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options, 70)); + it('should handle large paginated (50 pages) results when links are present', async () => { + requester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options, 70)); const response = await RequestHelper.get(service, 'test'); @@ -118,8 +125,8 @@ describe('RequestHelper.get()', () => { expect(response).toHaveLength(140); }); - it('Should be paginated but limited by the maxPages option', async () => { - KyRequester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); + it('should be paginated but limited by the maxPages option', async () => { + requester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); const response = await RequestHelper.get(service, 'test', { maxPages: 3 }); @@ -131,8 +138,8 @@ describe('RequestHelper.get()', () => { }); }); - it('Should be paginated but limited by the page option', async () => { - KyRequester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); + it('should be paginated but limited by the page option', async () => { + requester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); const response = await RequestHelper.get(service, 'test', { page: 2 }); @@ -144,10 +151,10 @@ describe('RequestHelper.get()', () => { }); }); - it('Should show the pagination information when the page option is given', async () => { - KyRequester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); + it('should show the pagination information when the page option is given', async () => { + requester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); - const response = await RequestHelper.get(service, 'test', { page: 2, showPagination: true }); + const response = await RequestHelper.get(service, 'test', { page: 2, showExpanded: true }); expect(response.data).toHaveLength(2); @@ -166,12 +173,12 @@ describe('RequestHelper.get()', () => { }); }); - it('Should show the pagination information when the maxPages option is given', async () => { - KyRequester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); + it('should show the pagination information when the maxPages option is given', async () => { + requester.get = jest.fn(({}, url, options) => mockedGetExtended(url, options)); const response = await RequestHelper.get(service, 'test', { maxPages: 3, - showPagination: true, + showExpanded: true, }); expect(response.data).toHaveLength(6); @@ -191,3 +198,59 @@ describe('RequestHelper.get()', () => { }); }); }); + +describe('RequestHelper.stream()', () => { + it('should throw an error when the stream function isnt available', async () => { + requester.stream = undefined; + + expect(() => RequestHelper.stream(service, 'test')).toThrow( + 'Stream method is not implementated in requester!', + ); + }); + + it('should not throw an error when the stream function is available', async () => { + requester.stream = jest.fn(); + + RequestHelper.stream(service, 'test'); + + expect(requester.stream).toBeCalled(); + }); +}); + +describe('RequestHelper.post()', () => { + it('should pass the correct arguments to the Requester', async () => { + requester.post = jest.fn(() => ({ body: '' })); + + await RequestHelper.post(service, 'test', { sudo: 'yes' }); + + expect(requester.post).toBeCalledWith(service, 'test', { body: {}, sudo: 'yes' }); + }); + + it('should by default pass the form, before the body if passed', async () => { + requester.post = jest.fn(() => ({ body: '' })); + + RequestHelper.post(service, 'test', { form: 1, test: 3 }); + + expect(requester.post).toBeCalledWith(service, 'test', { body: 1, sudo: undefined }); + }); +}); + +describe('RequestHelper.put()', () => { + it('should pass the correct arguments to the Requester', async () => { + requester.put = jest.fn(() => ({ body: '' })); + + await RequestHelper.put(service, 'test', { sudo: 'yes' }); + + expect(requester.put).toBeCalledWith(service, 'test', { body: {}, sudo: 'yes' }); + }); +}); + +describe('RequestHelper.del()', () => { + it('should pass the correct arguments to the Requester', async () => { + requester.delete = jest.fn(() => ({ body: '' })); + + await RequestHelper.del(service, 'test', { sudo: 'yes' }); + + expect(requester.delete).toBeCalledWith(service, 'test', { query: {}, sudo: 'yes' }); + }); +}); diff --git a/test/unit/infrastructure/Utils.ts b/packages/gitbeaker-core/test/unit/infrastructure/Utils.ts similarity index 86% rename from test/unit/infrastructure/Utils.ts rename to packages/gitbeaker-core/test/unit/infrastructure/Utils.ts index c1f1f42b..3f74f22f 100644 --- a/test/unit/infrastructure/Utils.ts +++ b/packages/gitbeaker-core/test/unit/infrastructure/Utils.ts @@ -1,14 +1,18 @@ -import { bundler } from '../../../src/core/infrastructure'; +import { bundler } from '../../../src/infrastructure'; /* eslint max-classes-per-file: 0 */ class Test1 { + public value: number; + constructor(value: number) { this.value = value * 3; } } class Test2 { + public value: number; + constructor(value: number) { this.value = value * 2; } diff --git a/test/unit/services/Pipelines.ts b/packages/gitbeaker-core/test/unit/services/Pipelines.ts similarity index 74% rename from test/unit/services/Pipelines.ts rename to packages/gitbeaker-core/test/unit/services/Pipelines.ts index 6d978f97..58b60dde 100644 --- a/test/unit/services/Pipelines.ts +++ b/packages/gitbeaker-core/test/unit/services/Pipelines.ts @@ -1,17 +1,19 @@ -import { Pipelines } from '../../../src/core'; -import { RequestHelper } from '../../../src/core/infrastructure'; +import { Pipelines } from '../../../src'; +import { RequestHelper } from '../../../src/infrastructure'; -jest.mock('../../../src/core/infrastructure/RequestHelper'); -jest.mock('../../../src/core/infrastructure/KyRequester', () => ({ - get: jest.fn(() => []), - post: jest.fn(() => {}), - put: jest.fn(() => {}), -})); +jest.mock('../../../src/infrastructure/RequestHelper'); let service: Pipelines; beforeEach(() => { + const requester = { + get: jest.fn(() => []), + post: jest.fn(() => ({})), + put: jest.fn(() => ({})), + }; + service = new Pipelines({ + requester, token: 'abcdefg', requestTimeout: 3000, }); diff --git a/test/unit/services/Projects.ts b/packages/gitbeaker-core/test/unit/services/Projects.ts similarity index 88% rename from test/unit/services/Projects.ts rename to packages/gitbeaker-core/test/unit/services/Projects.ts index ca493573..146e453b 100644 --- a/test/unit/services/Projects.ts +++ b/packages/gitbeaker-core/test/unit/services/Projects.ts @@ -1,18 +1,21 @@ -import { Projects } from '../../../src/core'; -import { RequestHelper } from '../../../src/core/infrastructure'; +import { Projects } from '../../../src'; +import { RequestHelper } from '../../../src/infrastructure'; -jest.mock('../../../src/core/infrastructure/RequestHelper'); -jest.mock('../../../src/core/infrastructure/KyRequester', () => ({ - get: jest.fn(() => []), - post: jest.fn(() => {}), - put: jest.fn(() => {}), -})); +jest.mock('../../../src/infrastructure/RequestHelper'); let service: Projects; beforeEach(() => { + const requester = { + get: jest.fn(() => []), + post: jest.fn(() => ({})), + put: jest.fn(() => ({})), + }; + service = new Projects({ + requester, token: 'abcdefg', + requestTimeout: 3000, }); }); diff --git a/packages/gitbeaker-core/tsconfig.json b/packages/gitbeaker-core/tsconfig.json new file mode 100644 index 00000000..8da2efa0 --- /dev/null +++ b/packages/gitbeaker-core/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declaration": true, + "declarationDir": "dist/types" + }, + "include": ["src"] +} diff --git a/packages/gitbeaker-node/package.json b/packages/gitbeaker-node/package.json new file mode 100644 index 00000000..0998250b --- /dev/null +++ b/packages/gitbeaker-node/package.json @@ -0,0 +1,50 @@ +{ + "name": "@gitbeaker/node", + "description": "Full NodeJS implementation of the GitLab API. Supports Promises, Async/Await.", + "version": "15.0.0", + "author": { + "name": "Justin Dalrymple" + }, + "bugs": { + "url": "https://github.com/jdalrymple/gitbeaker/issues" + }, + "dependencies": { + "@gitbeaker/core": "^15.0.0", + "@gitbeaker/requester-utils": "^15.0.0", + "form-data": "^3.0.0", + "got": "^10.4.0", + "xcase": "^2.0.1" + }, + "devDependencies": { + "@types/node": "^13.7.0", + "rollup": "^1.31.0", + "rollup-plugin-terser": "^5.2.0", + "rollup-plugin-typescript2": "^0.25.3", + "ts-node": "^8.6.2", + "typescript": "^3.7.5" + }, + "engines": { + "node": ">=10.0.0" + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/jdalrymple/gitbeaker#readme", + "keywords": [ + "api", + "es5", + "es6", + "gitlab", + "got" + ], + "license": "MIT", + "main": "dist/index.js", + "module": "dist/index.es.js", + "repository": { + "type": "git", + "url": "https://github.com/jdalrymple/gitbeaker" + }, + "scripts": { + "build": "tsc && rollup -c" + } +} diff --git a/packages/gitbeaker-node/rollup.config.js b/packages/gitbeaker-node/rollup.config.js new file mode 100644 index 00000000..09a5a3a1 --- /dev/null +++ b/packages/gitbeaker-node/rollup.config.js @@ -0,0 +1,18 @@ +import pkg from './package.json'; +import { commonConfig, commonPlugins } from '../../rollup.config'; + +export default { + ...commonConfig, + external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], + output: [ + { + file: pkg.main, // CommonJS (for Node) (for bundlers) build. + format: 'cjs', + }, + { + file: pkg.module, // ES module (for bundlers) build. + format: 'es', + }, + ], + plugins: commonPlugins, +}; diff --git a/packages/gitbeaker-node/src/GotRequester.ts b/packages/gitbeaker-node/src/GotRequester.ts new file mode 100644 index 00000000..3bac1809 --- /dev/null +++ b/packages/gitbeaker-node/src/GotRequester.ts @@ -0,0 +1,65 @@ +import Got from 'got'; +import FormData from 'form-data'; +import { decamelizeKeys } from 'xcase'; +import { + Service, + DefaultRequestOptions, + createInstance, + defaultRequest as baseDefaultRequest, +} from '@gitbeaker/requester-utils'; + +export function defaultRequest( + service: Service, + { body, query, sudo, method }: DefaultRequestOptions = {}, +) { + const options = baseDefaultRequest(service, { body, query, sudo, method }); + + if (typeof body === 'object' && !(body instanceof FormData)) { + options.json = decamelizeKeys(body); + + delete options.body; + } + + return options; +} + +export function processBody(response) { + const contentType = response.headers['content-type'] || ''; + + switch (contentType) { + case 'application/json': { + return response.body.length === 0 ? {} : JSON.parse(response.body.toString()); + } + case 'application/octet-stream': + case 'binary/octet-stream': + case 'application/gzip': { + return Buffer.from(response.body); + } + default: { + return response.body || ''; + } + } +} + +export async function handler(endpoint, options) { + let response; + + try { + response = await Got(endpoint, options); + } catch (e) { + if (e.response) { + const output = e.response.body; + + e.description = output.error || output.message; + } + + throw e; + } + + const { statusCode, headers } = response; + const body = processBody(response); + + return { body, headers, status: statusCode }; +} + +export const Requester = createInstance(defaultRequest, handler); diff --git a/packages/gitbeaker-node/src/index.ts b/packages/gitbeaker-node/src/index.ts new file mode 100644 index 00000000..23df541d --- /dev/null +++ b/packages/gitbeaker-node/src/index.ts @@ -0,0 +1,15 @@ +import * as Gitbeaker from '@gitbeaker/core'; +import { Requester } from './GotRequester'; + +const output = {}; + +Object.keys(Gitbeaker).forEach(name => { + output[name] = args => + new Gitbeaker[name]({ + requester: Requester, + ...args, + }); +}); + +/* eslint-disable */ +export default output; diff --git a/packages/gitbeaker-node/tsconfig.json b/packages/gitbeaker-node/tsconfig.json new file mode 100644 index 00000000..8da2efa0 --- /dev/null +++ b/packages/gitbeaker-node/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declaration": true, + "declarationDir": "dist/types" + }, + "include": ["src"] +} diff --git a/packages/gitbeaker-requester-utils/package.json b/packages/gitbeaker-requester-utils/package.json new file mode 100644 index 00000000..8cdc192c --- /dev/null +++ b/packages/gitbeaker-requester-utils/package.json @@ -0,0 +1,46 @@ +{ + "name": "@gitbeaker/requester-utils", + "description": "Utility functions for requester implementatons used in @gitbeaker", + "version": "15.0.0", + "author": { + "name": "Justin Dalrymple" + }, + "bugs": { + "url": "https://github.com/jdalrymple/gitbeaker/issues" + }, + "dependencies": { + "form-data": "^3.0.0", + "query-string": "^6.10.1", + "xcase": "^2.0.1" + }, + "devDependencies": { + "@types/node": "^13.7.0", + "rollup": "^1.31.0", + "rollup-plugin-terser": "^5.2.0", + "rollup-plugin-typescript2": "^0.25.3", + "ts-node": "^8.6.2", + "typescript": "^3.7.5" + }, + "engines": { + "node": ">=10.0.0" + }, + "files": [ + "dist" + ], + "homepage": "https://github.com/jdalrymple/gitbeaker#readme", + "keywords": [ + "gitbeaker", + "es5", + "es6" + ], + "license": "MIT", + "main": "dist/index.js", + "module": "dist/index.es.js", + "repository": { + "type": "git", + "url": "https://github.com/jdalrymple/gitbeaker" + }, + "scripts": { + "build": "tsc && rollup -c" + } +} diff --git a/packages/gitbeaker-requester-utils/rollup.config.js b/packages/gitbeaker-requester-utils/rollup.config.js new file mode 100644 index 00000000..09a5a3a1 --- /dev/null +++ b/packages/gitbeaker-requester-utils/rollup.config.js @@ -0,0 +1,18 @@ +import pkg from './package.json'; +import { commonConfig, commonPlugins } from '../../rollup.config'; + +export default { + ...commonConfig, + external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], + output: [ + { + file: pkg.main, // CommonJS (for Node) (for bundlers) build. + format: 'cjs', + }, + { + file: pkg.module, // ES module (for bundlers) build. + format: 'es', + }, + ], + plugins: commonPlugins, +}; diff --git a/packages/gitbeaker-requester-utils/src/RequesterUtils.ts b/packages/gitbeaker-requester-utils/src/RequesterUtils.ts new file mode 100644 index 00000000..125e54c5 --- /dev/null +++ b/packages/gitbeaker-requester-utils/src/RequesterUtils.ts @@ -0,0 +1,80 @@ +import FormData from 'form-data'; +import { decamelizeKeys } from 'xcase'; +import { Agent } from 'https'; +import { stringify } from 'query-string'; + +export interface RequesterType { + get(service: object, endpoint: string, options?: object): Promise; + post(service: object, endpoint: string, options?: object): Promise; + put(service: object, endpoint: string, options?: object): Promise; + delete(service: object, endpoint: string, options?: object): Promise; + stream?(service: object, endpoint: string, options?: object): Promise; +} + +export interface Service { + headers: Record; + requestTimeout: number; + url: string; + rejectUnauthorized?: boolean; +} + +export type DefaultRequestOptions = { + body?: FormData | object; + query?: object; + sudo?: string; + method: string; +}; + +const methods = ['get', 'post', 'put', 'delete', 'stream']; + +export function formatQuery(options) { + return stringify(decamelizeKeys(options || {}) as object, { arrayFormat: 'bracket' }); +} + +export function defaultRequest( + service: Service, + { body, query, sudo, method }: DefaultRequestOptions = { method: 'get' }, +): Record> { + const { headers } = service; + let agent; + let bod; + + if (sudo) headers.sudo = sudo; + + if (typeof body === 'object' && !(body instanceof FormData)) { + bod = JSON.stringify(decamelizeKeys(body)); + headers['content-type'] = 'application/json'; + } else { + bod = body; + } + + if (service.url.includes('https')) { + agent = new Agent({ + rejectUnauthorized: service.rejectUnauthorized, + }); + } + + return { + agent, + headers, + timeout: service.requestTimeout, + method, + searchParams: formatQuery(query), + prefixUrl: service.url, + body: bod, + }; +} + +export function createInstance(optionsHandler, requestHandler): RequesterType { + const requester: RequesterType = {} as RequesterType; + + methods.forEach(m => { + /* eslint func-names:0 */ + requester[m] = function(service, endpoint, options) { + const requestOptions = optionsHandler(service, { ...options, method: m }); + return requestHandler(endpoint, requestOptions); + }; + }); + + return requester; +} diff --git a/packages/gitbeaker-requester-utils/src/index.ts b/packages/gitbeaker-requester-utils/src/index.ts new file mode 100644 index 00000000..02a9c6ff --- /dev/null +++ b/packages/gitbeaker-requester-utils/src/index.ts @@ -0,0 +1 @@ +export * from './RequesterUtils'; diff --git a/packages/gitbeaker-requester-utils/test/unit/RequesterUtils.ts b/packages/gitbeaker-requester-utils/test/unit/RequesterUtils.ts new file mode 100644 index 00000000..545eefe1 --- /dev/null +++ b/packages/gitbeaker-requester-utils/test/unit/RequesterUtils.ts @@ -0,0 +1,99 @@ +import FormData from 'form-data'; +import { Agent } from 'https'; +import { createInstance, defaultRequest } from '../../src/RequesterUtils'; + +const methods = ['get', 'put', 'delete', 'stream', 'post']; + +describe('defaultRequest', () => { + const service = { + headers: { test: '5' }, + url: 'testurl', + rejectUnauthorized: false, + requestTimeout: 50, + }; + + it('should stringify body if it isnt of type FormData', async () => { + const testBody = { test: 6 }; + const { body, headers } = defaultRequest(service, { + method: 'post', + body: testBody, + }); + + expect(headers).toContainEntry(['content-type', 'application/json']); + expect(body).toBe(JSON.stringify(testBody)); + }); + + it('should not stringify body if it of type FormData', async () => { + const testBody = new FormData(); + const { body } = defaultRequest(service, { body: testBody, method: 'post' }); + + expect(body).toBeInstanceOf(FormData); + }); + + it('should not assign the agent property if given http url', async () => { + const options = defaultRequest(service, { method: 'post' }); + + expect(options.agent).toBeUndefined(); + }); + + it('should assign the agent property if given https url', async () => { + const options = defaultRequest({ ...service, url: 'https://test.com' }, { method: 'post' }); + + expect(options.agent).toBeInstanceOf(Agent); + expect(options.agent.rejectUnauthorized).toBeFalsy(); + }); + + it('should not assign the sudo property if omitted', async () => { + const { headers } = defaultRequest(service, { + sudo: undefined, + method: 'get', + }); + + expect(headers.sudo).toBeUndefined(); + }); + + it('should default searchParams to an empty string if undefined', async () => { + const { searchParams } = defaultRequest(service, { + query: undefined, + method: 'get', + }); + + expect(searchParams).toBe(''); + }); +}); + +describe('createInstance', () => { + const handler = jest.fn(); + const optionsHandler = jest.fn(() => ({})); + + it('should have a createInstance function', async () => { + expect(createInstance).toBeFunction(); + }); + + it('should return an object with function names equal to those in the methods array when the createInstance function is called', async () => { + const requester = createInstance(optionsHandler, handler); + + expect(requester).toContainAllKeys(methods); + + methods.forEach(m => { + expect(requester[m]).toBeFunction(); + }); + }); + + it('should call the handler with the correct endpoint when passed to any of the method functions', async () => { + const requester = createInstance(optionsHandler, handler); + const service = { + headers: { test: '5' }, + url: 'testurl', + rejectUnauthorized: false, + requestTimeout: 50, + }; + const testEndpoint = 'test endpoint'; + + methods.forEach(m => { + requester[m](service, testEndpoint, {}); + + expect(handler).toBeCalledWith(testEndpoint, {}); + }); + }); +}); diff --git a/packages/gitbeaker-requester-utils/tsconfig.json b/packages/gitbeaker-requester-utils/tsconfig.json new file mode 100644 index 00000000..8da2efa0 --- /dev/null +++ b/packages/gitbeaker-requester-utils/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./dist", + "declaration": true, + "declarationDir": "dist/types" + }, + "include": ["src"] +} diff --git a/rollup.config.js b/rollup.config.js index 1ac0f4ac..3458cb26 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,65 +1,8 @@ import ts from 'rollup-plugin-typescript2'; -import resolve from 'rollup-plugin-node-resolve'; -import commonjs from 'rollup-plugin-commonjs'; -import builtins from 'rollup-plugin-node-builtins'; -import globals from 'rollup-plugin-node-globals'; import { terser } from 'rollup-plugin-terser'; -import replace from 'rollup-plugin-replace'; import typescript from 'typescript'; -import pkg from './package.json'; -export default [ - // CLI build - { - input: 'src/bin/index.ts', - output: { - banner: '#!/usr/bin/env node', - file: pkg.bin.gitlab, - format: 'cjs', - }, - external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], - plugins: [ - replace({ - delimiters: ['', ''], - '#!/usr/bin/env node': '', - }), - ts({ typescript, useTsconfigDeclarationDir: true }), - terser(), - ], - }, - - // Browser-friendly UMD build - { - input: 'src/core/index.ts', - output: { - file: pkg.browser, - name: 'gitlab', - format: 'umd', - exports: 'named', - }, - plugins: [ - globals(), - builtins(), - resolve({ preferBuiltins: true, browser: true }), - commonjs(), - ts({ typescript, useTsconfigDeclarationDir: true }), - terser(), - ], - }, - - { - input: 'src/core/index.ts', - output: [ - { - file: pkg.main, // CommonJS (for Node) (for bundlers) build. - format: 'cjs', - }, - { - file: pkg.module, // ES module (for bundlers) build. - format: 'es', - }, - ], - external: [...Object.keys(pkg.dependencies || {}), ...Object.keys(pkg.peerDependencies || {})], - plugins: [ts({ typescript, useTsconfigDeclarationDir: true }), terser()], - }, -]; +export const commonPlugins = [ts({ typescript, useTsconfigDeclarationDir: true }), terser()]; +export const commonConfig = { + input: 'src/index.ts', +}; diff --git a/src/core/infrastructure/KyRequester.ts b/src/core/infrastructure/KyRequester.ts deleted file mode 100644 index 834bc52c..00000000 --- a/src/core/infrastructure/KyRequester.ts +++ /dev/null @@ -1,109 +0,0 @@ -import Ky from 'ky-universal'; -import FormData from 'form-data'; -import { decamelizeKeys } from 'xcase'; -import { stringify } from 'query-string'; -import { Agent } from 'https'; - -interface Service { - headers: object; - requestTimeout: number; - url: string; - rejectUnauthorized?: boolean; -} - -const methods = ['get', 'post', 'put', 'delete', 'stream']; -const KyRequester = {}; - -function responseHeadersAsObject(response): Record { - const headers = {}; - const keyVals = [...response.headers.entries()]; - - keyVals.forEach(([key, val]) => { - headers[key] = val; - }); - - return headers; -} - -function defaultRequest(service: Service, { body, query, sudo, method }) { - const headers = new Headers(service.headers as Record); - let bod = body; - let agent; - - if (sudo) headers.append('sudo', `${sudo}`); - - if (typeof body === 'object' && !(body instanceof FormData)) { - bod = JSON.stringify(decamelizeKeys(body)); - headers.append('content-type', 'application/json'); - } - - if (service.url.includes('https')) { - agent = new Agent({ - rejectUnauthorized: service.rejectUnauthorized, - }); - } - - return { - headers, - agent, - timeout: service.requestTimeout, - method: method === 'stream' ? 'get' : method, - onProgress: method === 'stream' ? () => {} : undefined, - searchParams: stringify(decamelizeKeys(query || {}) as object, { arrayFormat: 'bracket' }), - prefixUrl: service.url, - body: bod, - }; -} - -async function processBody(response) { - const contentType = response.headers.get('content-type') || ''; - - switch (contentType) { - case 'application/json': { - const json = await response.json(); - - return json || {}; - } - case 'application/octet-stream': - case 'binary/octet-stream': - case 'application/gzip': { - const blob = await response.blob(); - const arrayBuffer = await new Response(blob).arrayBuffer(); - - return Buffer.from(arrayBuffer); - } - default: { - const text = await response.text(); - - return text || ''; - } - } -} - -methods.forEach(m => { - /* eslint func-names:0 */ - KyRequester[m] = async function(service, endpoint, options) { - const requestOptions = defaultRequest(service, { ...options, method: m }); - let response; - - try { - response = await Ky(endpoint, requestOptions); - } catch (e) { - if (e.response) { - const output = await e.response.json(); - - e.description = output.error || output.message; - } - - throw e; - } - - const { status } = response; - const headers = responseHeadersAsObject(response); - const body = await processBody(response); - - return { body, headers, status }; - }; -}); - -export { KyRequester }; diff --git a/src/core/services/Deployments.ts b/src/core/services/Deployments.ts deleted file mode 100644 index d013a3d3..00000000 --- a/src/core/services/Deployments.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BaseService, RequestHelper, PaginatedRequestOptions, Sudo } from '../infrastructure'; - -export class Deployments extends BaseService { - all(projectId: string | number, options?: PaginatedRequestOptions) { - const pId = encodeURIComponent(projectId); - - return RequestHelper.get(this, `projects/${pId}/deployments`, options); - } - - show(projectId: string | number, deploymentId: number, options?: Sudo) { - const [pId, dId] = [projectId, deploymentId].map(encodeURIComponent); - - return RequestHelper.post(this, `projects/${pId}/deployments/${dId}`, options); - } -}