Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fd68362ee | |||
| 7ba0fc984e | |||
| 4d90bb01cf | |||
| e9dc0a7fa3 | |||
| 9feb49a3d4 | |||
| 879f16dd98 | |||
| e94004742f | |||
| 82940c7c0f | |||
| 016eaf1b91 | |||
| cbb9f364ae | |||
| ef688115d6 | |||
| 3aaa0f7e68 | |||
| 1cd7c62431 | |||
| 8f85104136 | |||
| c8059fb1c0 | |||
| 413c4be172 | |||
| e7cfd5dec1 | |||
| e002e90bd0 | |||
| f460b92c3a | |||
| 65e5575494 | |||
| 4da6b14dc2 | |||
| 95c5870252 | |||
| b0f56f5948 | |||
| cc00a78e2c | |||
| cf7e69e74a | |||
| 5cc3aeb27c | |||
| 098a9c64c6 | |||
| 1fd2bb8e01 | |||
| ca475fbfab | |||
| c8dc59cdf2 | |||
| 217724d836 | |||
| b0efd2671c | |||
| b80e63dc65 | |||
| 7765ff77f9 |
19
.gitignore
vendored
19
.gitignore
vendored
@@ -1,4 +1,19 @@
|
||||
node_modules/
|
||||
.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
#------# custom
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# gitzone standard
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
|
||||
cache:
|
||||
paths:
|
||||
- .yarn/
|
||||
key: "$CI_BUILD_STAGE"
|
||||
|
||||
stages:
|
||||
- test
|
||||
- release
|
||||
- trigger
|
||||
- pages
|
||||
|
||||
testLEGACY:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test legacy
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
allow_failure: true
|
||||
|
||||
testLTS:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test lts
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
|
||||
testSTABLE:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test stable
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci publish
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
|
||||
trigger:
|
||||
stage: trigger
|
||||
script:
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
stage: pages
|
||||
script:
|
||||
- npmci command yarn global add npmpage
|
||||
- npmci command npmpage
|
||||
tags:
|
||||
- docker
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
80
changelog.md
Normal file
80
changelog.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-02 - 2.5.0 - feat(gitlab)
|
||||
add pipelines and jobs API support, including list/get/trigger/delete/retry/cancel operations, job controls, and related types and list options
|
||||
|
||||
- Add new pipeline methods: getPipelines (with filtering, ordering and pagination), getPipeline, triggerPipeline, deletePipeline, retryPipeline, cancelPipeline, getPipelineVariables, getPipelineTestReport
|
||||
- Add new job methods: getPipelineJobs (with scope filtering and pagination), getJob, retryJob, cancelJob, playJob, eraseJob; preserve getJobLog
|
||||
- Introduce IPipelineListOptions and IJobListOptions for richer listing filters
|
||||
- Extend and add interfaces: IGitLabPipeline (additional metadata fields), IGitLabJob (expanded fields including pipeline, runner, artifacts), IGitLabPipelineVariable, IGitLabTestReport, IGitLabTestSuite, IGitLabTestCase and other minor interface reorganizations
|
||||
- Export the new interfaces from the package entry (ts/index.ts)
|
||||
|
||||
## 2026-03-02 - 2.4.0 - feat(gitlab)
|
||||
add repository branches and tags endpoints and corresponding types
|
||||
|
||||
- Add getRepoBranches(projectId, opts?) to list repository branches (supports pagination with page and perPage; defaults to page=1, perPage=50).
|
||||
- Add getRepoTags(projectId, opts?) to list repository tags (supports pagination with page and perPage; defaults to page=1, perPage=50).
|
||||
- Introduce IGitLabBranch and IGitLabTag interfaces and export them from ts/index.ts.
|
||||
|
||||
## 2026-02-28 - 2.3.0 - feat(gitlab)
|
||||
add support for GitLab protected branches (list and unprotect)
|
||||
|
||||
- Added IGitLabProtectedBranch interface
|
||||
- Added GitLabClient.getProtectedBranches(projectId) returning IGitLabProtectedBranch[]
|
||||
- Added GitLabClient.unprotectBranch(projectId, branchName) to remove branch protection
|
||||
- Exported IGitLabProtectedBranch from index.ts
|
||||
- Non-breaking API additions; recommended minor version bump
|
||||
|
||||
## 2026-02-28 - 2.2.0 - feat(gitlabclient)
|
||||
add deleteProject method to delete a project using GitLab API DELETE /api/v4/projects/:id
|
||||
|
||||
- Adds GitlabClient.deleteProject(projectId: number | string): Promise<void>.
|
||||
- Implements DELETE /api/v4/projects/:id and URL-encodes the projectId.
|
||||
- Non-breaking API addition — bump minor version.
|
||||
|
||||
## 2026-02-28 - 2.1.0 - feat(gitlab)
|
||||
add group- and project-management methods to GitLab client
|
||||
|
||||
- Adds getGroupByPath(fullPath) to fetch a group by full path
|
||||
- Adds getGroupProjects(groupId, opts?) to list projects in a group (include_subgroups=true, default perPage=50, optional search)
|
||||
- Adds getDescendantGroups(groupId, opts?) to list descendant subgroups (paginated, default perPage=50, optional search)
|
||||
- Adds createGroup(name, path, parentId?) to create groups (defaults to private visibility)
|
||||
- Adds createProject(name, opts?) to create projects with optional path, namespaceId, visibility (default private) and description
|
||||
|
||||
## 2026-02-24 - 2.0.3 - fix()
|
||||
no changes detected; nothing to commit
|
||||
|
||||
- No files changed in the provided diff
|
||||
- No updates to source, dependencies, or package files
|
||||
|
||||
## 2026-02-24 - 2.0.2 - fix()
|
||||
no changes detected in diff; no version bump required
|
||||
|
||||
- No files changed in this commit/diff.
|
||||
- No code or dependency changes detected; no release necessary.
|
||||
|
||||
## 2026-02-24 - 2.0.1 - fix()
|
||||
no changes detected; no version bump required
|
||||
|
||||
- No files changed in diff
|
||||
- package.json current version is 2.0.0
|
||||
|
||||
## 2026-02-24 - 2.0.0 - gitlab
|
||||
Renamed package to @apiclient.xyz/gitlab and introduced a new ESM TypeScript GitLabClient API. Breaking release that replaces the legacy @mojoio/gitlab client.
|
||||
|
||||
- BREAKING CHANGE: package renamed to @apiclient.xyz/gitlab and API migrated to a new GitLabClient (v2.0.0).
|
||||
- New modern ESM TypeScript client with support for projects, groups, CI/CD variables, and pipelines.
|
||||
- fix(core): add npm release registry configuration (release tooling improvement).
|
||||
|
||||
## 2021-10-05 - 1.0.4..1.0.10 - core maintenance
|
||||
Series of patch releases containing maintenance updates and small fixes across the 1.0.x line.
|
||||
|
||||
- Multiple "fix(core): update" commits applying minor fixes and internal updates across 1.0.4–1.0.10.
|
||||
- Mostly patch-level changes and version bumps; no documented breaking or feature changes.
|
||||
|
||||
## 2017-06-10 - 1.0.1..1.0.3 - initial release & docs
|
||||
Initial project setup and first releases.
|
||||
|
||||
- 1.0.1 — initial commit.
|
||||
- 1.0.2 — add README.
|
||||
- 1.0.3 — patch/versioned release.
|
||||
21
license.md
Normal file
21
license.md
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Lossless GmbH (https://lossless.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -1,18 +1,35 @@
|
||||
{
|
||||
"gitzone": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "mojoio",
|
||||
"gitrepo": "gitlab",
|
||||
"shortDescription": "a gitlab api abstraction package",
|
||||
"npmPackagename": "@mojoio/gitlab",
|
||||
"license": "MIT",
|
||||
"projectDomain": "mojo.io"
|
||||
}
|
||||
},
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public"
|
||||
},
|
||||
"gitzone": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "apiclient.xyz",
|
||||
"gitrepo": "gitlab",
|
||||
"description": "A TypeScript client for the GitLab API, providing easy access to projects, groups, CI/CD variables, and pipelines.",
|
||||
"npmPackagename": "@apiclient.xyz/gitlab",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"gitlab",
|
||||
"api client",
|
||||
"TypeScript",
|
||||
"CI/CD",
|
||||
"pipelines",
|
||||
"variables",
|
||||
"devops"
|
||||
]
|
||||
}
|
||||
},
|
||||
"@git.zone/cli": {
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
}
|
||||
}
|
||||
11625
package-lock.json
generated
11625
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@@ -1,25 +1,55 @@
|
||||
{
|
||||
"name": "@mojoio/gitlab",
|
||||
"version": "1.0.4",
|
||||
"description": "api abstraction package for gitlab",
|
||||
"name": "@apiclient.xyz/gitlab",
|
||||
"version": "2.6.0",
|
||||
"private": false,
|
||||
"description": "A TypeScript client for the GitLab API, providing easy access to projects, groups, CI/CD variables, and pipelines.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --verbose --timeout 600)",
|
||||
"build": "(tsbuild --web --allowimplicitany)"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://code.foss.global/apiclient.xyz/gitlab.git"
|
||||
},
|
||||
"keywords": [
|
||||
"gitlab",
|
||||
"api client",
|
||||
"TypeScript",
|
||||
"CI/CD",
|
||||
"pipelines",
|
||||
"variables",
|
||||
"devops"
|
||||
],
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/)",
|
||||
"build": "(tsbuild --web)"
|
||||
"dependencies": {
|
||||
"@push.rocks/smartlog": "^3.1.10",
|
||||
"@push.rocks/smartrequest": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gitzone/tsbuild": "^2.1.25",
|
||||
"@gitzone/tsrun": "^1.2.12",
|
||||
"@gitzone/tstest": "^1.0.54",
|
||||
"@pushrocks/tapbundle": "^3.2.14",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0"
|
||||
"@git.zone/tsbuild": "^3.1.0",
|
||||
"@git.zone/tsrun": "^2.0.0",
|
||||
"@git.zone/tstest": "^2.8.2",
|
||||
"@push.rocks/qenv": "^6.1.3",
|
||||
"@push.rocks/tapbundle": "^5.6.0",
|
||||
"@types/node": "^22.15.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pushrocks/smartrequest": "^1.1.51",
|
||||
"@pushrocks/smarturl": "^2.0.1"
|
||||
}
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
]
|
||||
}
|
||||
|
||||
9914
pnpm-lock.yaml
generated
Normal file
9914
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
616
readme.md
616
readme.md
@@ -1,29 +1,601 @@
|
||||
# glab
|
||||
api abstraction package for gitlab
|
||||
# @apiclient.xyz/gitlab
|
||||
|
||||
## Availabililty
|
||||
[](https://www.npmjs.com/package/glab)
|
||||
[](https://GitLab.com/mojoio/glab)
|
||||
[](https://github.com/mojoio/glab)
|
||||
[](https://mojoio.gitlab.io/glab/)
|
||||
A powerful, fully-typed TypeScript client for the GitLab API 🚀
|
||||
|
||||
## Status for master
|
||||
[](https://GitLab.com/mojoio/glab/commits/master)
|
||||
[](https://GitLab.com/mojoio/glab/commits/master)
|
||||
[](https://www.npmjs.com/package/glab)
|
||||
[](https://david-dm.org/mojoio/glab)
|
||||
[](https://www.bithound.io/github/mojoio/glab/master/dependencies/npm)
|
||||
[](https://www.bithound.io/github/mojoio/glab)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](http://standardjs.com/)
|
||||
[](https://www.npmjs.com/package/@apiclient.xyz/gitlab)
|
||||
[](./LICENSE)
|
||||
|
||||
`@apiclient.xyz/gitlab` gives you a clean, object-oriented interface to the GitLab REST API. It wraps projects, groups, pipelines, jobs, CI/CD variables, branches, tags, protected branches, and test reports into rich domain classes with full auto-pagination support. Works with any GitLab instance -- cloud or self-hosted.
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @apiclient.xyz/gitlab
|
||||
# or
|
||||
pnpm install @apiclient.xyz/gitlab
|
||||
```
|
||||
|
||||
## Quick Start ⚡
|
||||
|
||||
```typescript
|
||||
import { GitLabClient } from '@apiclient.xyz/gitlab';
|
||||
|
||||
const client = new GitLabClient('https://gitlab.example.com', 'your-private-token');
|
||||
|
||||
// Verify connectivity
|
||||
const { ok, error } = await client.testConnection();
|
||||
console.log(ok ? '✅ Connected!' : `❌ ${error}`);
|
||||
|
||||
// Grab all your projects
|
||||
const projects = await client.getProjects();
|
||||
for (const project of projects) {
|
||||
console.log(`${project.name} — ${project.webUrl}`);
|
||||
}
|
||||
```
|
||||
|
||||
That's it. No boilerplate, no callback soup, just straightforward async/await.
|
||||
|
||||
## Core Concepts 🧠
|
||||
|
||||
### Rich Domain Objects
|
||||
|
||||
Every API response is wrapped in a **domain class** that exposes typed properties and chainable methods. You never deal with raw JSON unless you want to -- every class has a `.toJSON()` escape hatch.
|
||||
|
||||
```
|
||||
GitLabClient
|
||||
├── GitLabGroup → .getProjects(), .getVariables(), .update(), .delete(), ...
|
||||
├── GitLabProject → .getPipelines(), .getBranches(), .getVariables(), .triggerPipeline(), ...
|
||||
│ ├── GitLabBranch
|
||||
│ ├── GitLabTag
|
||||
│ ├── GitLabProtectedBranch
|
||||
│ └── GitLabVariable
|
||||
├── GitLabPipeline → .getJobs(), .getTestReport(), .retry(), .cancel(), .delete()
|
||||
│ ├── GitLabJob → .getLog(), .retry(), .cancel(), .play(), .erase()
|
||||
│ ├── GitLabPipelineVariable
|
||||
│ └── GitLabTestReport
|
||||
│ └── GitLabTestSuite
|
||||
│ └── GitLabTestCase
|
||||
└── GitLabVariable
|
||||
```
|
||||
|
||||
### Auto-Pagination 🔄
|
||||
|
||||
List endpoints automatically page through **all** results by default. Need just one page? Pass `{ page: N }` explicitly.
|
||||
|
||||
```typescript
|
||||
// Fetches ALL groups (auto-paginates transparently)
|
||||
const allGroups = await client.getGroups();
|
||||
|
||||
// Fetches only page 2, 10 per page
|
||||
const page2 = await client.getGroups({ page: 2, perPage: 10 });
|
||||
```
|
||||
|
||||
The `autoPaginate` helper is also exported if you want to use it in custom integrations.
|
||||
|
||||
## Usage
|
||||
Use TypeScript for best in class instellisense.
|
||||
|
||||
For further information read the linked docs at the top of this README.
|
||||
### Creating a Client
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
||||
```typescript
|
||||
import { GitLabClient } from '@apiclient.xyz/gitlab';
|
||||
|
||||
[](https://mojo.io)
|
||||
const client = new GitLabClient('https://gitlab.example.com', 'your-private-token');
|
||||
```
|
||||
|
||||
The constructor takes the base URL of your GitLab instance and a personal access token (or project/group token) for `PRIVATE-TOKEN` header authentication.
|
||||
|
||||
### Testing the Connection
|
||||
|
||||
```typescript
|
||||
const result = await client.testConnection();
|
||||
|
||||
if (result.ok) {
|
||||
console.log('Connected successfully.');
|
||||
} else {
|
||||
console.error('Connection failed:', result.error);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Groups 🏢
|
||||
|
||||
#### List All Groups
|
||||
|
||||
```typescript
|
||||
const groups = await client.getGroups();
|
||||
|
||||
// Search by name
|
||||
const devGroups = await client.getGroups({ search: 'platform' });
|
||||
|
||||
for (const group of devGroups) {
|
||||
console.log(`${group.id} — ${group.fullPath} (${group.visibility})`);
|
||||
}
|
||||
```
|
||||
|
||||
#### Get a Single Group
|
||||
|
||||
```typescript
|
||||
const group = await client.getGroup('my-org/my-team');
|
||||
console.log(group.name, group.webUrl);
|
||||
```
|
||||
|
||||
#### Create a Group
|
||||
|
||||
```typescript
|
||||
const newGroup = await client.createGroup('backend-team', 'backend-team');
|
||||
|
||||
// Create a sub-group under an existing parent
|
||||
const subGroup = await client.createGroup('api-squad', 'api-squad', parentGroup.id);
|
||||
```
|
||||
|
||||
#### Group Operations
|
||||
|
||||
```typescript
|
||||
// Update properties
|
||||
await group.update({ description: 'The backend crew 🔧', visibility: 'internal' });
|
||||
|
||||
// Upload / remove avatar
|
||||
await group.setAvatar(imageBytes, 'logo.png');
|
||||
await group.deleteAvatar();
|
||||
|
||||
// Transfer to another parent group
|
||||
await group.transfer(targetGroupId);
|
||||
|
||||
// List descendant sub-groups
|
||||
const children = await group.getDescendantGroups();
|
||||
|
||||
// List projects inside the group
|
||||
const projects = await group.getProjects({ search: 'api' });
|
||||
|
||||
// Delete
|
||||
await group.delete();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Projects 📦
|
||||
|
||||
#### List All Projects
|
||||
|
||||
```typescript
|
||||
const projects = await client.getProjects();
|
||||
|
||||
// Search with pagination
|
||||
const matches = await client.getProjects({ search: 'my-service', perPage: 20 });
|
||||
|
||||
for (const p of matches) {
|
||||
console.log(`${p.id} — ${p.fullPath} [${p.defaultBranch}]`);
|
||||
}
|
||||
```
|
||||
|
||||
#### Get a Single Project
|
||||
|
||||
```typescript
|
||||
// By numeric ID
|
||||
const project = await client.getProject(42);
|
||||
|
||||
// By full path
|
||||
const project = await client.getProject('my-org/my-repo');
|
||||
```
|
||||
|
||||
#### Create a Project
|
||||
|
||||
```typescript
|
||||
const project = await client.createProject('new-service', {
|
||||
namespaceId: group.id,
|
||||
visibility: 'internal',
|
||||
description: 'Our shiny new microservice',
|
||||
});
|
||||
```
|
||||
|
||||
#### Project Operations
|
||||
|
||||
```typescript
|
||||
// Update properties
|
||||
await project.update({
|
||||
description: 'Updated description',
|
||||
defaultBranch: 'develop',
|
||||
topics: ['typescript', 'api'],
|
||||
});
|
||||
|
||||
// Avatar management
|
||||
await project.setAvatar(imageBytes, 'project-logo.png');
|
||||
await project.deleteAvatar();
|
||||
|
||||
// Transfer to a different namespace
|
||||
await project.transfer(otherNamespaceId);
|
||||
|
||||
// Delete
|
||||
await project.delete();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Branches & Tags 🌿
|
||||
|
||||
```typescript
|
||||
// List all branches (auto-paginated)
|
||||
const branches = await project.getBranches();
|
||||
for (const b of branches) {
|
||||
console.log(`${b.name} @ ${b.commitSha}`);
|
||||
}
|
||||
|
||||
// List all tags
|
||||
const tags = await project.getTags();
|
||||
for (const t of tags) {
|
||||
console.log(`${t.name} @ ${t.commitSha}`);
|
||||
}
|
||||
```
|
||||
|
||||
#### Protected Branches 🔒
|
||||
|
||||
```typescript
|
||||
const protectedBranches = await project.getProtectedBranches();
|
||||
for (const pb of protectedBranches) {
|
||||
console.log(`${pb.name} — force push: ${pb.allowForcePush}`);
|
||||
}
|
||||
|
||||
// Remove branch protection
|
||||
await project.unprotectBranch('develop');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### CI/CD Variables 🔑
|
||||
|
||||
Variables work identically on both projects and groups.
|
||||
|
||||
#### Project Variables
|
||||
|
||||
```typescript
|
||||
// List all
|
||||
const vars = await project.getVariables();
|
||||
|
||||
// Create
|
||||
const dbVar = await project.createVariable('DATABASE_URL', 'postgres://localhost/db', {
|
||||
protected: true,
|
||||
masked: true,
|
||||
environment_scope: 'production',
|
||||
});
|
||||
|
||||
// Update
|
||||
await project.updateVariable('DATABASE_URL', 'postgres://newhost/db', {
|
||||
protected: true,
|
||||
});
|
||||
|
||||
// Delete
|
||||
await project.deleteVariable('DATABASE_URL');
|
||||
```
|
||||
|
||||
#### Group Variables
|
||||
|
||||
```typescript
|
||||
const groupVars = await group.getVariables();
|
||||
|
||||
await group.createVariable('NPM_TOKEN', 'tok-xxx', {
|
||||
protected: false,
|
||||
masked: true,
|
||||
environment_scope: '*',
|
||||
});
|
||||
|
||||
await group.updateVariable('NPM_TOKEN', 'tok-yyy');
|
||||
await group.deleteVariable('NPM_TOKEN');
|
||||
```
|
||||
|
||||
Each `GitLabVariable` instance exposes typed properties:
|
||||
|
||||
```typescript
|
||||
console.log(dbVar.key); // 'DATABASE_URL'
|
||||
console.log(dbVar.value); // 'postgres://...'
|
||||
console.log(dbVar.variableType); // 'env_var'
|
||||
console.log(dbVar.protected); // true
|
||||
console.log(dbVar.masked); // true
|
||||
console.log(dbVar.environmentScope); // 'production'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Pipelines 🚀
|
||||
|
||||
#### List & Filter Pipelines
|
||||
|
||||
```typescript
|
||||
// Recent pipelines (auto-paginated)
|
||||
const pipelines = await project.getPipelines();
|
||||
|
||||
// Advanced filtering
|
||||
const failed = await project.getPipelines({
|
||||
status: 'failed',
|
||||
ref: 'main',
|
||||
source: 'push',
|
||||
orderBy: 'updated_at',
|
||||
sort: 'desc',
|
||||
updatedAfter: '2025-01-01T00:00:00Z',
|
||||
});
|
||||
|
||||
for (const p of failed) {
|
||||
console.log(`Pipeline #${p.id} [${p.status}] on ${p.ref} — ${p.webUrl}`);
|
||||
}
|
||||
```
|
||||
|
||||
#### Trigger a Pipeline
|
||||
|
||||
```typescript
|
||||
const pipeline = await project.triggerPipeline('main', [
|
||||
{ key: 'DEPLOY_ENV', value: 'staging' },
|
||||
]);
|
||||
console.log(`Triggered pipeline #${pipeline.id}`);
|
||||
```
|
||||
|
||||
#### Pipeline Actions
|
||||
|
||||
```typescript
|
||||
// Retry all failed jobs
|
||||
const retried = await pipeline.retry();
|
||||
|
||||
// Cancel a running pipeline
|
||||
const cancelled = await pipeline.cancel();
|
||||
|
||||
// Delete a pipeline
|
||||
await pipeline.delete();
|
||||
```
|
||||
|
||||
#### Pipeline Variables & Test Reports
|
||||
|
||||
```typescript
|
||||
// Get variables used in a pipeline run
|
||||
const pipelineVars = await pipeline.getVariables();
|
||||
for (const v of pipelineVars) {
|
||||
console.log(`${v.key} = ${v.value} (${v.variableType})`);
|
||||
}
|
||||
|
||||
// Get the test report
|
||||
const report = await pipeline.getTestReport();
|
||||
console.log(`Tests: ${report.totalCount} total, ${report.successCount} passed, ${report.failedCount} failed`);
|
||||
|
||||
for (const suite of report.testSuites) {
|
||||
console.log(` Suite "${suite.name}": ${suite.totalCount} tests (${suite.failedCount} failures)`);
|
||||
for (const tc of suite.testCases) {
|
||||
if (tc.status === 'failed') {
|
||||
console.log(` ❌ ${tc.name}: ${tc.systemOutput}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Jobs ⚙️
|
||||
|
||||
```typescript
|
||||
// Get all jobs for a pipeline
|
||||
const jobs = await pipeline.getJobs();
|
||||
|
||||
// Filter by scope
|
||||
const failedJobs = await pipeline.getJobs({ scope: ['failed'] });
|
||||
|
||||
for (const job of jobs) {
|
||||
console.log(`Job "${job.name}" (${job.stage}) — ${job.status} [${job.duration}s]`);
|
||||
}
|
||||
```
|
||||
|
||||
#### Job Actions
|
||||
|
||||
```typescript
|
||||
// Read the raw log output
|
||||
const log = await job.getLog();
|
||||
console.log(log);
|
||||
|
||||
// Retry a failed job
|
||||
const retriedJob = await job.retry();
|
||||
|
||||
// Cancel a running job
|
||||
const cancelledJob = await job.cancel();
|
||||
|
||||
// Trigger a manual job
|
||||
const played = await job.play();
|
||||
|
||||
// Erase job artifacts and trace
|
||||
await job.erase();
|
||||
```
|
||||
|
||||
#### Job Properties
|
||||
|
||||
Each `GitLabJob` exposes rich metadata:
|
||||
|
||||
```typescript
|
||||
job.id // number
|
||||
job.name // 'build'
|
||||
job.stage // 'test'
|
||||
job.status // 'success' | 'failed' | 'running' | ...
|
||||
job.ref // 'main'
|
||||
job.tag // false
|
||||
job.webUrl // full URL to the job
|
||||
job.duration // seconds
|
||||
job.queuedDuration // seconds waiting in queue
|
||||
job.coverage // code coverage percentage
|
||||
job.allowFailure // whether failure is acceptable
|
||||
job.failureReason // reason for failure, if any
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Reference 📚
|
||||
|
||||
### `GitLabClient`
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `new GitLabClient(baseUrl, token)` | `GitLabClient` | Create a client instance |
|
||||
| `.testConnection()` | `Promise<ITestConnectionResult>` | Verify token and connectivity |
|
||||
| `.getGroups(opts?)` | `Promise<GitLabGroup[]>` | List all accessible groups (auto-paginated) |
|
||||
| `.getGroup(fullPath)` | `Promise<GitLabGroup>` | Get a single group by full path |
|
||||
| `.createGroup(name, path, parentId?)` | `Promise<GitLabGroup>` | Create a new group |
|
||||
| `.getProjects(opts?)` | `Promise<GitLabProject[]>` | List your projects (auto-paginated) |
|
||||
| `.getProject(idOrPath)` | `Promise<GitLabProject>` | Get a single project by ID or path |
|
||||
| `.createProject(name, opts?)` | `Promise<GitLabProject>` | Create a new project |
|
||||
|
||||
### `GitLabGroup`
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `.getProjects(opts?)` | `Promise<GitLabProject[]>` | List projects in the group |
|
||||
| `.getDescendantGroups(opts?)` | `Promise<GitLabGroup[]>` | List descendant sub-groups |
|
||||
| `.getVariables()` | `Promise<GitLabVariable[]>` | List CI/CD variables |
|
||||
| `.createVariable(key, value, opts?)` | `Promise<GitLabVariable>` | Create a CI/CD variable |
|
||||
| `.updateVariable(key, value, opts?)` | `Promise<GitLabVariable>` | Update a CI/CD variable |
|
||||
| `.deleteVariable(key)` | `Promise<void>` | Delete a CI/CD variable |
|
||||
| `.update(data)` | `Promise<void>` | Update group properties |
|
||||
| `.setAvatar(imageData, filename)` | `Promise<void>` | Upload avatar image |
|
||||
| `.deleteAvatar()` | `Promise<void>` | Remove avatar |
|
||||
| `.transfer(parentGroupId)` | `Promise<void>` | Transfer to another parent group |
|
||||
| `.delete()` | `Promise<void>` | Delete the group |
|
||||
| `.toJSON()` | `IGitLabGroup` | Serialize to raw interface |
|
||||
|
||||
### `GitLabProject`
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `.getBranches(opts?)` | `Promise<GitLabBranch[]>` | List repository branches |
|
||||
| `.getTags(opts?)` | `Promise<GitLabTag[]>` | List repository tags |
|
||||
| `.getProtectedBranches()` | `Promise<GitLabProtectedBranch[]>` | List protected branches |
|
||||
| `.unprotectBranch(name)` | `Promise<void>` | Remove branch protection |
|
||||
| `.getVariables()` | `Promise<GitLabVariable[]>` | List CI/CD variables |
|
||||
| `.createVariable(key, value, opts?)` | `Promise<GitLabVariable>` | Create a CI/CD variable |
|
||||
| `.updateVariable(key, value, opts?)` | `Promise<GitLabVariable>` | Update a CI/CD variable |
|
||||
| `.deleteVariable(key)` | `Promise<void>` | Delete a CI/CD variable |
|
||||
| `.getPipelines(opts?)` | `Promise<GitLabPipeline[]>` | List pipelines (with rich filtering) |
|
||||
| `.triggerPipeline(ref, variables?)` | `Promise<GitLabPipeline>` | Trigger a new pipeline |
|
||||
| `.update(data)` | `Promise<void>` | Update project properties |
|
||||
| `.setAvatar(imageData, filename)` | `Promise<void>` | Upload avatar image |
|
||||
| `.deleteAvatar()` | `Promise<void>` | Remove avatar |
|
||||
| `.transfer(namespaceId)` | `Promise<void>` | Transfer to another namespace |
|
||||
| `.delete()` | `Promise<void>` | Delete the project |
|
||||
| `.toJSON()` | `IGitLabProject` | Serialize to raw interface |
|
||||
|
||||
### `GitLabPipeline`
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `.getJobs(opts?)` | `Promise<GitLabJob[]>` | List jobs (with scope filtering) |
|
||||
| `.getVariables()` | `Promise<GitLabPipelineVariable[]>` | Get pipeline variables |
|
||||
| `.getTestReport()` | `Promise<GitLabTestReport>` | Get the test report |
|
||||
| `.retry()` | `Promise<GitLabPipeline>` | Retry failed jobs |
|
||||
| `.cancel()` | `Promise<GitLabPipeline>` | Cancel the pipeline |
|
||||
| `.delete()` | `Promise<void>` | Delete the pipeline |
|
||||
| `.toJSON()` | `IGitLabPipeline` | Serialize to raw interface |
|
||||
|
||||
### `GitLabJob`
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `.getLog()` | `Promise<string>` | Get raw job trace/log |
|
||||
| `.retry()` | `Promise<GitLabJob>` | Retry the job |
|
||||
| `.cancel()` | `Promise<GitLabJob>` | Cancel the job |
|
||||
| `.play()` | `Promise<GitLabJob>` | Trigger a manual job |
|
||||
| `.erase()` | `Promise<void>` | Erase artifacts and trace |
|
||||
| `.toJSON()` | `IGitLabJob` | Serialize to raw interface |
|
||||
|
||||
### Value Classes
|
||||
|
||||
| Class | Key Properties |
|
||||
|---|---|
|
||||
| `GitLabBranch` | `name`, `commitSha` |
|
||||
| `GitLabTag` | `name`, `commitSha` |
|
||||
| `GitLabProtectedBranch` | `id`, `name`, `allowForcePush` |
|
||||
| `GitLabVariable` | `key`, `value`, `variableType`, `protected`, `masked`, `environmentScope` |
|
||||
| `GitLabPipelineVariable` | `key`, `value`, `variableType` |
|
||||
| `GitLabTestReport` | `totalTime`, `totalCount`, `successCount`, `failedCount`, `skippedCount`, `errorCount`, `testSuites` |
|
||||
| `GitLabTestSuite` | `name`, `totalTime`, `totalCount`, `successCount`, `failedCount`, `skippedCount`, `errorCount`, `testCases` |
|
||||
| `GitLabTestCase` | `status`, `name`, `classname`, `executionTime`, `systemOutput`, `stackTrace` |
|
||||
|
||||
---
|
||||
|
||||
## TypeScript Interfaces 🏗️
|
||||
|
||||
All raw GitLab API shapes are exported as TypeScript interfaces so you can use them in your own type definitions.
|
||||
|
||||
### `IListOptions`
|
||||
|
||||
```typescript
|
||||
interface IListOptions {
|
||||
search?: string; // Filter results by keyword
|
||||
page?: number; // Page number (default: 1)
|
||||
perPage?: number; // Items per page (default: 50)
|
||||
}
|
||||
```
|
||||
|
||||
### `IPipelineListOptions`
|
||||
|
||||
Extends `IListOptions` with pipeline-specific filters:
|
||||
|
||||
```typescript
|
||||
interface IPipelineListOptions extends IListOptions {
|
||||
status?: string; // Filter by pipeline status
|
||||
ref?: string; // Filter by branch/tag ref
|
||||
source?: string; // Filter by trigger source (push, web, schedule, api, ...)
|
||||
scope?: string; // Filter by scope (running, pending, finished, branches, tags)
|
||||
username?: string; // Filter by the triggering user
|
||||
updatedAfter?: string; // ISO 8601 date — only pipelines updated after this
|
||||
updatedBefore?: string; // ISO 8601 date — only pipelines updated before this
|
||||
orderBy?: string; // Order by field (id, status, ref, updated_at, user_id)
|
||||
sort?: string; // Sort direction (asc, desc)
|
||||
}
|
||||
```
|
||||
|
||||
### `IJobListOptions`
|
||||
|
||||
```typescript
|
||||
interface IJobListOptions extends IListOptions {
|
||||
scope?: string[]; // Filter by job scope(s): created, pending, running, failed, success, ...
|
||||
}
|
||||
```
|
||||
|
||||
### `IVariableOptions`
|
||||
|
||||
```typescript
|
||||
interface IVariableOptions {
|
||||
protected?: boolean; // Only expose to protected branches/tags
|
||||
masked?: boolean; // Mask the value in job logs
|
||||
environment_scope?: string; // Environment scope (default: '*')
|
||||
}
|
||||
```
|
||||
|
||||
### `ITestConnectionResult`
|
||||
|
||||
```typescript
|
||||
interface ITestConnectionResult {
|
||||
ok: boolean;
|
||||
error?: string; // Present when ok is false
|
||||
}
|
||||
```
|
||||
|
||||
All raw data interfaces (`IGitLabProject`, `IGitLabGroup`, `IGitLabPipeline`, `IGitLabJob`, `IGitLabVariable`, `IGitLabBranch`, `IGitLabTag`, `IGitLabProtectedBranch`, `IGitLabPipelineVariable`, `IGitLabTestReport`, `IGitLabTestSuite`, `IGitLabTestCase`, `IGitLabUser`) are also exported for advanced use cases.
|
||||
|
||||
---
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||
|
||||
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||
|
||||
### Trademarks
|
||||
|
||||
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||
|
||||
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||
|
||||
34
test/test.node.ts
Normal file
34
test/test.node.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as qenv from '@push.rocks/qenv';
|
||||
import { GitLabClient } from '../ts/index.js';
|
||||
|
||||
const testQenv = new qenv.Qenv('./', '.nogit/');
|
||||
|
||||
let gitlabClient: GitLabClient;
|
||||
|
||||
tap.test('should create a GitLabClient instance', async () => {
|
||||
const baseUrl = (await testQenv.getEnvVarOnDemand('GITLAB_BASE_URL')) || 'https://gitlab.com';
|
||||
const token = (await testQenv.getEnvVarOnDemand('GITLAB_TOKEN')) || '';
|
||||
gitlabClient = new GitLabClient(baseUrl, token);
|
||||
expect(gitlabClient).toBeInstanceOf(GitLabClient);
|
||||
});
|
||||
|
||||
tap.test('should test connection', async () => {
|
||||
const result = await gitlabClient.testConnection();
|
||||
expect(result).toHaveProperty('ok');
|
||||
console.log('Connection test:', result);
|
||||
});
|
||||
|
||||
tap.test('should get projects', async () => {
|
||||
const projects = await gitlabClient.getProjects({ perPage: 5 });
|
||||
expect(projects).toBeArray();
|
||||
console.log(`Found ${projects.length} projects`);
|
||||
});
|
||||
|
||||
tap.test('should get groups', async () => {
|
||||
const groups = await gitlabClient.getGroups({ perPage: 5 });
|
||||
expect(groups).toBeArray();
|
||||
console.log(`Found ${groups.length} groups`);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
23
test/test.ts
23
test/test.ts
@@ -1,23 +0,0 @@
|
||||
import { expect, tap } from '@pushrocks/tapbundle'
|
||||
import * as gitlab from '../ts/index';
|
||||
|
||||
let testGitlabAccount: gitlab.GitlabAccount;
|
||||
|
||||
tap.test('should create an anonymous Gitlab Account', async () => {
|
||||
testGitlabAccount = gitlab.GitlabAccount.createAnonymousAccount();
|
||||
expect(testGitlabAccount).to.be.instanceOf(gitlab.GitlabAccount);
|
||||
});
|
||||
|
||||
tap.test('should get the pushrocks group', async () => {
|
||||
const pushrocksGroup = await testGitlabAccount.getGroupByName('pushrocks');
|
||||
expect(pushrocksGroup).to.be.instanceOf(gitlab.GitlabGroup);
|
||||
console.log(pushrocksGroup);
|
||||
});
|
||||
|
||||
tap.test('should get the pushrocks group', async () => {
|
||||
const pushrocksGroup = await testGitlabAccount.getGroupByName('pushrocks');
|
||||
expect(pushrocksGroup).to.be.instanceOf(gitlab.GitlabGroup);
|
||||
await pushrocksGroup.getProjects();
|
||||
});
|
||||
|
||||
tap.start();
|
||||
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@apiclient.xyz/gitlab',
|
||||
version: '2.5.0',
|
||||
description: 'A TypeScript client for the GitLab API, providing easy access to projects, groups, CI/CD variables, and pipelines.'
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import { GitlabGroup } from './gitlab.classes.group';
|
||||
import * as plugins from './gitlab.plugins';
|
||||
|
||||
export class GitlabAccount {
|
||||
public static createAnonymousAccount() {
|
||||
return new GitlabAccount();
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public async getGroupByName(nameArg: string): Promise<GitlabGroup> {
|
||||
return GitlabGroup.getByName(nameArg, this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* handles the basic request/response patterns with the gitlab.com API
|
||||
*/
|
||||
public async request (methodArg: 'GET' | 'POST', routeArg: string, searchParamsArg: {[key: string]: string}) {
|
||||
if(!routeArg.startsWith('/')) {
|
||||
throw new Error(`"${routeArg}" -> routeArg must start with a slash`);
|
||||
}
|
||||
const smarturlInstance = plugins.smarturl.Smarturl.createFromUrl(`https://gitlab.com/api/v4${routeArg}`, {
|
||||
searchParams: searchParamsArg
|
||||
});
|
||||
const response = await plugins.smartrequest.request(smarturlInstance.toString(), {
|
||||
method: methodArg
|
||||
});
|
||||
|
||||
// lets deal with pagination headers
|
||||
const fintLinkName = (markup) => {
|
||||
const pattern = /<([^\s>]+)(\s|>)+/;
|
||||
return markup.match(pattern)[1];
|
||||
};
|
||||
if (typeof response.headers.link === 'string') {
|
||||
const links = response.headers.link.split(',');
|
||||
const linkObjects: {
|
||||
original: string;
|
||||
link: string;
|
||||
}[] = [];
|
||||
}
|
||||
return response.body;
|
||||
}
|
||||
}
|
||||
18
ts/gitlab.classes.branch.ts
Normal file
18
ts/gitlab.classes.branch.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { IGitLabBranch } from './gitlab.interfaces.js';
|
||||
|
||||
export class GitLabBranch {
|
||||
public readonly name: string;
|
||||
public readonly commitSha: string;
|
||||
|
||||
constructor(raw: IGitLabBranch) {
|
||||
this.name = raw.name || '';
|
||||
this.commitSha = raw.commit?.id || '';
|
||||
}
|
||||
|
||||
toJSON(): IGitLabBranch {
|
||||
return {
|
||||
name: this.name,
|
||||
commit: { id: this.commitSha },
|
||||
};
|
||||
}
|
||||
}
|
||||
665
ts/gitlab.classes.gitlabclient.ts
Normal file
665
ts/gitlab.classes.gitlabclient.ts
Normal file
@@ -0,0 +1,665 @@
|
||||
import * as plugins from './gitlab.plugins.js';
|
||||
import type {
|
||||
IGitLabUser,
|
||||
IGitLabProject,
|
||||
IGitLabGroup,
|
||||
IGitLabVariable,
|
||||
IVariableOptions,
|
||||
IGitLabProtectedBranch,
|
||||
IGitLabBranch,
|
||||
IGitLabTag,
|
||||
IGitLabPipeline,
|
||||
IGitLabPipelineVariable,
|
||||
IGitLabTestReport,
|
||||
IGitLabJob,
|
||||
ITestConnectionResult,
|
||||
IListOptions,
|
||||
IPipelineListOptions,
|
||||
IJobListOptions,
|
||||
} from './gitlab.interfaces.js';
|
||||
import { GitLabGroup } from './gitlab.classes.group.js';
|
||||
import { GitLabProject } from './gitlab.classes.project.js';
|
||||
import { GitLabPipeline } from './gitlab.classes.pipeline.js';
|
||||
import { autoPaginate } from './gitlab.helpers.js';
|
||||
|
||||
export class GitLabClient {
|
||||
private baseUrl: string;
|
||||
private token: string;
|
||||
|
||||
constructor(baseUrl: string, token: string) {
|
||||
this.baseUrl = baseUrl.replace(/\/+$/, '');
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// HTTP helpers (internal)
|
||||
// ===========================================================================
|
||||
|
||||
/** @internal */
|
||||
async request<T = any>(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
||||
path: string,
|
||||
data?: any,
|
||||
customHeaders?: Record<string, string>,
|
||||
): Promise<T> {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
|
||||
let builder = plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.header('PRIVATE-TOKEN', this.token)
|
||||
.header('Content-Type', 'application/json');
|
||||
|
||||
if (customHeaders) {
|
||||
for (const [k, v] of Object.entries(customHeaders)) {
|
||||
builder = builder.header(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
if (data) {
|
||||
builder = builder.json(data);
|
||||
}
|
||||
|
||||
let response: Awaited<ReturnType<typeof builder.get>>;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await builder.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await builder.post();
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await builder.put();
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await builder.delete();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return await response.json() as T;
|
||||
} catch {
|
||||
return undefined as unknown as T;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestText(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
|
||||
path: string,
|
||||
): Promise<string> {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
|
||||
let builder = plugins.smartrequest.SmartRequest.create()
|
||||
.url(url)
|
||||
.header('PRIVATE-TOKEN', this.token)
|
||||
.header('Accept', 'text/plain');
|
||||
|
||||
let response: Awaited<ReturnType<typeof builder.get>>;
|
||||
switch (method) {
|
||||
case 'GET':
|
||||
response = await builder.get();
|
||||
break;
|
||||
case 'POST':
|
||||
response = await builder.post();
|
||||
break;
|
||||
case 'PUT':
|
||||
response = await builder.put();
|
||||
break;
|
||||
case 'DELETE':
|
||||
response = await builder.delete();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
|
||||
return response.text();
|
||||
}
|
||||
|
||||
/** @internal — multipart form upload (for avatars) */
|
||||
async requestMultipart<T = any>(
|
||||
method: 'PUT' | 'POST',
|
||||
path: string,
|
||||
formData: FormData,
|
||||
): Promise<T> {
|
||||
const url = `${this.baseUrl}${path}`;
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers: { 'PRIVATE-TOKEN': this.token },
|
||||
body: formData,
|
||||
});
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
throw new Error(`${method} ${path}: ${response.status} ${response.statusText} - ${errorText}`);
|
||||
}
|
||||
try {
|
||||
return await response.json() as T;
|
||||
} catch {
|
||||
return undefined as unknown as T;
|
||||
}
|
||||
}
|
||||
|
||||
/** @internal — fetch binary data (e.g. avatar images) */
|
||||
async requestBinary(url: string): Promise<Uint8Array> {
|
||||
const fullUrl = url.startsWith('http') ? url : `${this.baseUrl}${url}`;
|
||||
const response = await fetch(fullUrl, {
|
||||
headers: { 'PRIVATE-TOKEN': this.token },
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`GET ${url}: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
const buf = await response.arrayBuffer();
|
||||
return new Uint8Array(buf);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Public API — Connection
|
||||
// ===========================================================================
|
||||
|
||||
public async testConnection(): Promise<ITestConnectionResult> {
|
||||
try {
|
||||
await this.request<IGitLabUser>('GET', '/api/v4/user');
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Public API — Groups (returns rich objects)
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Get all groups (auto-paginated).
|
||||
*/
|
||||
public async getGroups(opts?: IListOptions): Promise<GitLabGroup[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.requestGetGroups({ ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(groups => groups.map(g => new GitLabGroup(this, g)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single group by full path.
|
||||
*/
|
||||
public async getGroup(fullPath: string): Promise<GitLabGroup> {
|
||||
const raw = await this.requestGetGroupByPath(fullPath);
|
||||
return new GitLabGroup(this, raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new group.
|
||||
*/
|
||||
public async createGroup(name: string, path: string, parentId?: number): Promise<GitLabGroup> {
|
||||
const raw = await this.requestCreateGroup(name, path, parentId);
|
||||
return new GitLabGroup(this, raw);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Public API — Projects (returns rich objects)
|
||||
// ===========================================================================
|
||||
|
||||
/**
|
||||
* Get all projects (auto-paginated, membership=true).
|
||||
*/
|
||||
public async getProjects(opts?: IListOptions): Promise<GitLabProject[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.requestGetProjects({ ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(projects => projects.map(p => new GitLabProject(this, p)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single project by ID or path.
|
||||
*/
|
||||
public async getProject(idOrPath: number | string): Promise<GitLabProject> {
|
||||
const raw = await this.requestGetProject(idOrPath);
|
||||
return new GitLabProject(this, raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new project.
|
||||
*/
|
||||
public async createProject(name: string, opts?: {
|
||||
path?: string;
|
||||
namespaceId?: number;
|
||||
visibility?: string;
|
||||
description?: string;
|
||||
}): Promise<GitLabProject> {
|
||||
const raw = await this.requestCreateProject(name, opts);
|
||||
return new GitLabProject(this, raw);
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Internal request methods — called by domain classes
|
||||
// ===========================================================================
|
||||
|
||||
// --- Groups ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetGroups(opts?: IListOptions): Promise<IGitLabGroup[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
let url = `/api/v4/groups?order_by=name&sort=asc&page=${page}&per_page=${perPage}`;
|
||||
if (opts?.search) {
|
||||
url += `&search=${encodeURIComponent(opts.search)}`;
|
||||
}
|
||||
return this.request<IGitLabGroup[]>('GET', url);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetGroupByPath(fullPath: string): Promise<IGitLabGroup> {
|
||||
return this.request<IGitLabGroup>('GET', `/api/v4/groups/${encodeURIComponent(fullPath)}`);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetGroupProjects(groupId: number | string, opts?: IListOptions): Promise<IGitLabProject[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
let url = `/api/v4/groups/${encodeURIComponent(groupId)}/projects?include_subgroups=true&order_by=updated_at&sort=desc&page=${page}&per_page=${perPage}`;
|
||||
if (opts?.search) {
|
||||
url += `&search=${encodeURIComponent(opts.search)}`;
|
||||
}
|
||||
return this.request<IGitLabProject[]>('GET', url);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetDescendantGroups(groupId: number | string, opts?: IListOptions): Promise<IGitLabGroup[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
let url = `/api/v4/groups/${encodeURIComponent(groupId)}/descendant_groups?order_by=name&sort=asc&page=${page}&per_page=${perPage}`;
|
||||
if (opts?.search) {
|
||||
url += `&search=${encodeURIComponent(opts.search)}`;
|
||||
}
|
||||
return this.request<IGitLabGroup[]>('GET', url);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestCreateGroup(name: string, path: string, parentId?: number): Promise<IGitLabGroup> {
|
||||
const body: any = { name, path, visibility: 'private' };
|
||||
if (parentId) body.parent_id = parentId;
|
||||
return this.request<IGitLabGroup>('POST', '/api/v4/groups', body);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestUpdateGroup(groupId: number | string, data: Record<string, any>): Promise<void> {
|
||||
await this.request('PUT', `/api/v4/groups/${encodeURIComponent(groupId)}`, data);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestSetGroupAvatar(groupId: number | string, imageData: Uint8Array, filename: string): Promise<void> {
|
||||
const blob = new Blob([imageData.buffer as ArrayBuffer]);
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', blob, filename);
|
||||
await this.requestMultipart('PUT', `/api/v4/groups/${encodeURIComponent(groupId)}`, formData);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestTransferGroup(groupId: number | string, parentGroupId: number): Promise<void> {
|
||||
await this.request('POST', `/api/v4/groups/${encodeURIComponent(groupId)}/transfer`, {
|
||||
group_id: parentGroupId,
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestDeleteGroup(groupId: number | string): Promise<void> {
|
||||
await this.request('DELETE', `/api/v4/groups/${encodeURIComponent(groupId)}`);
|
||||
}
|
||||
|
||||
// --- Projects ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetProjects(opts?: IListOptions): Promise<IGitLabProject[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
let url = `/api/v4/projects?membership=true&order_by=updated_at&sort=desc&page=${page}&per_page=${perPage}`;
|
||||
if (opts?.search) {
|
||||
url += `&search=${encodeURIComponent(opts.search)}`;
|
||||
}
|
||||
return this.request<IGitLabProject[]>('GET', url);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetProject(idOrPath: number | string): Promise<IGitLabProject> {
|
||||
return this.request<IGitLabProject>('GET', `/api/v4/projects/${encodeURIComponent(idOrPath)}`);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestCreateProject(name: string, opts?: {
|
||||
path?: string;
|
||||
namespaceId?: number;
|
||||
visibility?: string;
|
||||
description?: string;
|
||||
}): Promise<IGitLabProject> {
|
||||
return this.request<IGitLabProject>('POST', '/api/v4/projects', {
|
||||
name,
|
||||
path: opts?.path || name,
|
||||
namespace_id: opts?.namespaceId,
|
||||
visibility: opts?.visibility || 'private',
|
||||
description: opts?.description || '',
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestUpdateProject(projectId: number | string, data: Record<string, any>): Promise<void> {
|
||||
await this.request('PUT', `/api/v4/projects/${encodeURIComponent(projectId)}`, data);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestSetProjectAvatar(projectId: number | string, imageData: Uint8Array, filename: string): Promise<void> {
|
||||
const blob = new Blob([imageData.buffer as ArrayBuffer]);
|
||||
const formData = new FormData();
|
||||
formData.append('avatar', blob, filename);
|
||||
await this.requestMultipart('PUT', `/api/v4/projects/${encodeURIComponent(projectId)}`, formData);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestTransferProject(projectId: number | string, namespaceId: number): Promise<void> {
|
||||
await this.request('PUT', `/api/v4/projects/${encodeURIComponent(projectId)}/transfer`, {
|
||||
namespace: namespaceId,
|
||||
});
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestDeleteProject(projectId: number | string): Promise<void> {
|
||||
await this.request('DELETE', `/api/v4/projects/${encodeURIComponent(projectId)}`);
|
||||
}
|
||||
|
||||
// --- Repo Branches & Tags ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetRepoBranches(projectId: number | string, opts?: IListOptions): Promise<IGitLabBranch[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
return this.request<IGitLabBranch[]>(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/repository/branches?page=${page}&per_page=${perPage}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetRepoTags(projectId: number | string, opts?: IListOptions): Promise<IGitLabTag[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 50;
|
||||
return this.request<IGitLabTag[]>(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/repository/tags?page=${page}&per_page=${perPage}`,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Protected Branches ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetProtectedBranches(projectId: number | string): Promise<IGitLabProtectedBranch[]> {
|
||||
return this.request<IGitLabProtectedBranch[]>(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/protected_branches`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestUnprotectBranch(projectId: number | string, branchName: string): Promise<void> {
|
||||
await this.request(
|
||||
'DELETE',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/protected_branches/${encodeURIComponent(branchName)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Project Variables ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetProjectVariables(projectId: number | string): Promise<IGitLabVariable[]> {
|
||||
return this.request<IGitLabVariable[]>(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/variables`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestCreateProjectVariable(
|
||||
projectId: number | string,
|
||||
key: string,
|
||||
value: string,
|
||||
opts?: IVariableOptions,
|
||||
): Promise<IGitLabVariable> {
|
||||
return this.request<IGitLabVariable>(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/variables`,
|
||||
{
|
||||
key,
|
||||
value,
|
||||
protected: opts?.protected ?? false,
|
||||
masked: opts?.masked ?? false,
|
||||
environment_scope: opts?.environment_scope ?? '*',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestUpdateProjectVariable(
|
||||
projectId: number | string,
|
||||
key: string,
|
||||
value: string,
|
||||
opts?: IVariableOptions,
|
||||
): Promise<IGitLabVariable> {
|
||||
const body: any = { value };
|
||||
if (opts?.protected !== undefined) body.protected = opts.protected;
|
||||
if (opts?.masked !== undefined) body.masked = opts.masked;
|
||||
if (opts?.environment_scope !== undefined) body.environment_scope = opts.environment_scope;
|
||||
return this.request<IGitLabVariable>(
|
||||
'PUT',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/variables/${encodeURIComponent(key)}`,
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestDeleteProjectVariable(projectId: number | string, key: string): Promise<void> {
|
||||
await this.request(
|
||||
'DELETE',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/variables/${encodeURIComponent(key)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Group Variables ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetGroupVariables(groupId: number | string): Promise<IGitLabVariable[]> {
|
||||
return this.request<IGitLabVariable[]>(
|
||||
'GET',
|
||||
`/api/v4/groups/${encodeURIComponent(groupId)}/variables`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestCreateGroupVariable(
|
||||
groupId: number | string,
|
||||
key: string,
|
||||
value: string,
|
||||
opts?: IVariableOptions,
|
||||
): Promise<IGitLabVariable> {
|
||||
return this.request<IGitLabVariable>(
|
||||
'POST',
|
||||
`/api/v4/groups/${encodeURIComponent(groupId)}/variables`,
|
||||
{
|
||||
key,
|
||||
value,
|
||||
protected: opts?.protected ?? false,
|
||||
masked: opts?.masked ?? false,
|
||||
environment_scope: opts?.environment_scope ?? '*',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestUpdateGroupVariable(
|
||||
groupId: number | string,
|
||||
key: string,
|
||||
value: string,
|
||||
opts?: IVariableOptions,
|
||||
): Promise<IGitLabVariable> {
|
||||
const body: any = { value };
|
||||
if (opts?.protected !== undefined) body.protected = opts.protected;
|
||||
if (opts?.masked !== undefined) body.masked = opts.masked;
|
||||
if (opts?.environment_scope !== undefined) body.environment_scope = opts.environment_scope;
|
||||
return this.request<IGitLabVariable>(
|
||||
'PUT',
|
||||
`/api/v4/groups/${encodeURIComponent(groupId)}/variables/${encodeURIComponent(key)}`,
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestDeleteGroupVariable(groupId: number | string, key: string): Promise<void> {
|
||||
await this.request(
|
||||
'DELETE',
|
||||
`/api/v4/groups/${encodeURIComponent(groupId)}/variables/${encodeURIComponent(key)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Pipelines ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetPipelines(projectId: number | string, opts?: IPipelineListOptions): Promise<IGitLabPipeline[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 30;
|
||||
const orderBy = opts?.orderBy || 'updated_at';
|
||||
const sort = opts?.sort || 'desc';
|
||||
let url = `/api/v4/projects/${encodeURIComponent(projectId)}/pipelines?page=${page}&per_page=${perPage}&order_by=${orderBy}&sort=${sort}`;
|
||||
if (opts?.status) url += `&status=${encodeURIComponent(opts.status)}`;
|
||||
if (opts?.ref) url += `&ref=${encodeURIComponent(opts.ref)}`;
|
||||
if (opts?.source) url += `&source=${encodeURIComponent(opts.source)}`;
|
||||
if (opts?.scope) url += `&scope=${encodeURIComponent(opts.scope)}`;
|
||||
if (opts?.username) url += `&username=${encodeURIComponent(opts.username)}`;
|
||||
if (opts?.updatedAfter) url += `&updated_after=${encodeURIComponent(opts.updatedAfter)}`;
|
||||
if (opts?.updatedBefore) url += `&updated_before=${encodeURIComponent(opts.updatedBefore)}`;
|
||||
return this.request<IGitLabPipeline[]>('GET', url);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetPipeline(projectId: number | string, pipelineId: number): Promise<IGitLabPipeline> {
|
||||
return this.request<IGitLabPipeline>(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestTriggerPipeline(
|
||||
projectId: number | string,
|
||||
ref: string,
|
||||
variables?: { key: string; value: string; variable_type?: string }[],
|
||||
): Promise<IGitLabPipeline> {
|
||||
const body: any = { ref };
|
||||
if (variables && variables.length > 0) {
|
||||
body.variables = variables;
|
||||
}
|
||||
return this.request<IGitLabPipeline>(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/pipeline`,
|
||||
body,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestRetryPipeline(projectId: number | string, pipelineId: number): Promise<IGitLabPipeline> {
|
||||
return this.request<IGitLabPipeline>(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/retry`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestCancelPipeline(projectId: number | string, pipelineId: number): Promise<IGitLabPipeline> {
|
||||
return this.request<IGitLabPipeline>(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/cancel`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestDeletePipeline(projectId: number | string, pipelineId: number): Promise<void> {
|
||||
await this.request(
|
||||
'DELETE',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetPipelineVariables(projectId: number | string, pipelineId: number): Promise<IGitLabPipelineVariable[]> {
|
||||
return this.request<IGitLabPipelineVariable[]>(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/variables`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetPipelineTestReport(projectId: number | string, pipelineId: number): Promise<IGitLabTestReport> {
|
||||
return this.request<IGitLabTestReport>(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/test_report`,
|
||||
);
|
||||
}
|
||||
|
||||
// --- Jobs ---
|
||||
|
||||
/** @internal */
|
||||
async requestGetPipelineJobs(projectId: number | string, pipelineId: number, opts?: IJobListOptions): Promise<IGitLabJob[]> {
|
||||
const page = opts?.page || 1;
|
||||
const perPage = opts?.perPage || 100;
|
||||
let url = `/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/jobs?page=${page}&per_page=${perPage}`;
|
||||
if (opts?.scope && opts.scope.length > 0) {
|
||||
for (const s of opts.scope) {
|
||||
url += `&scope[]=${encodeURIComponent(s)}`;
|
||||
}
|
||||
}
|
||||
return this.request<IGitLabJob[]>('GET', url);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestGetJobLog(projectId: number | string, jobId: number): Promise<string> {
|
||||
return this.requestText(
|
||||
'GET',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/trace`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestRetryJob(projectId: number | string, jobId: number): Promise<IGitLabJob> {
|
||||
return this.request<IGitLabJob>(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/retry`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestCancelJob(projectId: number | string, jobId: number): Promise<IGitLabJob> {
|
||||
return this.request<IGitLabJob>(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/cancel`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestPlayJob(projectId: number | string, jobId: number): Promise<IGitLabJob> {
|
||||
return this.request<IGitLabJob>(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/play`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
async requestEraseJob(projectId: number | string, jobId: number): Promise<void> {
|
||||
await this.request(
|
||||
'POST',
|
||||
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/erase`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,137 @@
|
||||
import * as plugins from './gitlab.plugins';
|
||||
import { GitlabAccount } from './gitlab.classes.account';
|
||||
import { GitlabProject } from './gitlab.classes.project';
|
||||
import type { GitLabClient } from './gitlab.classes.gitlabclient.js';
|
||||
import type { IGitLabGroup, IListOptions, IVariableOptions } from './gitlab.interfaces.js';
|
||||
import { GitLabProject } from './gitlab.classes.project.js';
|
||||
import { GitLabVariable } from './gitlab.classes.variable.js';
|
||||
import { autoPaginate } from './gitlab.helpers.js';
|
||||
|
||||
export interface IGitlabGroup {
|
||||
id: number;
|
||||
web_url: string;
|
||||
name: string;
|
||||
path: string;
|
||||
description: string;
|
||||
visibility: string;
|
||||
share_with_group_lock: string;
|
||||
require_two_factor_authentication: string;
|
||||
two_factor_grace_period: number;
|
||||
project_creation_level: string;
|
||||
auto_devops_enabled: null;
|
||||
subgroup_creation_level: string;
|
||||
emails_disabled: null;
|
||||
mentions_disabled: null;
|
||||
lfs_enabled: boolean;
|
||||
default_branch_protection: number;
|
||||
avatar_url: string;
|
||||
request_access_enabled: boolean;
|
||||
full_name: string;
|
||||
full_path: string;
|
||||
created_at: string;
|
||||
parent_id: null;
|
||||
ldap_cn: null;
|
||||
ldap_access: null;
|
||||
}
|
||||
export class GitLabGroup {
|
||||
// Raw data
|
||||
public readonly id: number;
|
||||
public readonly name: string;
|
||||
public readonly fullPath: string;
|
||||
public readonly description: string;
|
||||
public readonly webUrl: string;
|
||||
public readonly visibility: string;
|
||||
|
||||
export class GitlabGroup {
|
||||
public static async getByName(nameArg: string, gitlabAccountArg: GitlabAccount) {
|
||||
const response = await gitlabAccountArg.request('GET', '/groups', {
|
||||
search: 'pushrocks',
|
||||
/** @internal */
|
||||
constructor(
|
||||
private client: GitLabClient,
|
||||
raw: IGitLabGroup,
|
||||
) {
|
||||
this.id = raw.id;
|
||||
this.name = raw.name || '';
|
||||
this.fullPath = raw.full_path || '';
|
||||
this.description = raw.description || '';
|
||||
this.webUrl = raw.web_url || '';
|
||||
this.visibility = raw.visibility || 'private';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Projects
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getProjects(opts?: IListOptions): Promise<GitLabProject[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.client.requestGetGroupProjects(this.id, { ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(projects => projects.map(p => new GitLabProject(this.client, p)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Descendant Groups
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getDescendantGroups(opts?: IListOptions): Promise<GitLabGroup[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.client.requestGetDescendantGroups(this.id, { ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(groups => groups.map(g => new GitLabGroup(this.client, g)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Variables (CI/CD)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getVariables(): Promise<GitLabVariable[]> {
|
||||
const vars = await this.client.requestGetGroupVariables(this.id);
|
||||
return vars.map(v => new GitLabVariable(v));
|
||||
}
|
||||
|
||||
async createVariable(key: string, value: string, opts?: IVariableOptions): Promise<GitLabVariable> {
|
||||
const raw = await this.client.requestCreateGroupVariable(this.id, key, value, opts);
|
||||
return new GitLabVariable(raw);
|
||||
}
|
||||
|
||||
async updateVariable(key: string, value: string, opts?: IVariableOptions): Promise<GitLabVariable> {
|
||||
const raw = await this.client.requestUpdateGroupVariable(this.id, key, value, opts);
|
||||
return new GitLabVariable(raw);
|
||||
}
|
||||
|
||||
async deleteVariable(key: string): Promise<void> {
|
||||
await this.client.requestDeleteGroupVariable(this.id, key);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mutation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Update group properties.
|
||||
*/
|
||||
async update(data: {
|
||||
name?: string;
|
||||
path?: string;
|
||||
description?: string;
|
||||
visibility?: string;
|
||||
}): Promise<void> {
|
||||
await this.client.requestUpdateGroup(this.id, {
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
description: data.description,
|
||||
visibility: data.visibility,
|
||||
});
|
||||
// console.log(response);
|
||||
const returnGroup = new GitlabGroup(response[0], gitlabAccountArg);
|
||||
return returnGroup;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public gitlabAccountRef: GitlabAccount;
|
||||
public data: IGitlabGroup;
|
||||
|
||||
constructor(dataArg: IGitlabGroup, gitlabAccountArg: GitlabAccount) {
|
||||
this.gitlabAccountRef = gitlabAccountArg;
|
||||
this.data = dataArg;
|
||||
/**
|
||||
* Upload an avatar image for this group (multipart FormData).
|
||||
*/
|
||||
async setAvatar(imageData: Uint8Array, filename: string): Promise<void> {
|
||||
await this.client.requestSetGroupAvatar(this.id, imageData, filename);
|
||||
}
|
||||
|
||||
public async getProjects() {
|
||||
return GitlabProject.getProjectsForGroup(this);
|
||||
/**
|
||||
* Remove the group's avatar.
|
||||
*/
|
||||
async deleteAvatar(): Promise<void> {
|
||||
await this.client.requestUpdateGroup(this.id, { avatar: '' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer this group to be a child of another group.
|
||||
*/
|
||||
async transfer(parentGroupId: number): Promise<void> {
|
||||
await this.client.requestTransferGroup(this.id, parentGroupId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this group.
|
||||
*/
|
||||
async delete(): Promise<void> {
|
||||
await this.client.requestDeleteGroup(this.id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serialization
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
toJSON(): IGitLabGroup {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
full_path: this.fullPath,
|
||||
description: this.description,
|
||||
web_url: this.webUrl,
|
||||
visibility: this.visibility,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
104
ts/gitlab.classes.job.ts
Normal file
104
ts/gitlab.classes.job.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import type { GitLabClient } from './gitlab.classes.gitlabclient.js';
|
||||
import type { IGitLabJob } from './gitlab.interfaces.js';
|
||||
|
||||
export class GitLabJob {
|
||||
// Raw data
|
||||
public readonly id: number;
|
||||
public readonly name: string;
|
||||
public readonly stage: string;
|
||||
public readonly status: string;
|
||||
public readonly ref: string;
|
||||
public readonly tag: boolean;
|
||||
public readonly webUrl: string;
|
||||
public readonly createdAt: string;
|
||||
public readonly startedAt: string;
|
||||
public readonly finishedAt: string;
|
||||
public readonly duration: number;
|
||||
public readonly queuedDuration: number;
|
||||
public readonly coverage: number;
|
||||
public readonly allowFailure: boolean;
|
||||
public readonly failureReason: string;
|
||||
|
||||
/** @internal */
|
||||
constructor(
|
||||
private client: GitLabClient,
|
||||
private projectId: number | string,
|
||||
raw: IGitLabJob,
|
||||
) {
|
||||
this.id = raw.id;
|
||||
this.name = raw.name || '';
|
||||
this.stage = raw.stage || '';
|
||||
this.status = raw.status || '';
|
||||
this.ref = raw.ref || '';
|
||||
this.tag = raw.tag ?? false;
|
||||
this.webUrl = raw.web_url || '';
|
||||
this.createdAt = raw.created_at || '';
|
||||
this.startedAt = raw.started_at || '';
|
||||
this.finishedAt = raw.finished_at || '';
|
||||
this.duration = raw.duration || 0;
|
||||
this.queuedDuration = raw.queued_duration || 0;
|
||||
this.coverage = raw.coverage || 0;
|
||||
this.allowFailure = raw.allow_failure ?? false;
|
||||
this.failureReason = raw.failure_reason || '';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Log
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getLog(): Promise<string> {
|
||||
return this.client.requestGetJobLog(this.projectId, this.id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Actions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async retry(): Promise<GitLabJob> {
|
||||
const raw = await this.client.requestRetryJob(this.projectId, this.id);
|
||||
return new GitLabJob(this.client, this.projectId, raw);
|
||||
}
|
||||
|
||||
async cancel(): Promise<GitLabJob> {
|
||||
const raw = await this.client.requestCancelJob(this.projectId, this.id);
|
||||
return new GitLabJob(this.client, this.projectId, raw);
|
||||
}
|
||||
|
||||
async play(): Promise<GitLabJob> {
|
||||
const raw = await this.client.requestPlayJob(this.projectId, this.id);
|
||||
return new GitLabJob(this.client, this.projectId, raw);
|
||||
}
|
||||
|
||||
async erase(): Promise<void> {
|
||||
await this.client.requestEraseJob(this.projectId, this.id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serialization
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
toJSON(): IGitLabJob {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
stage: this.stage,
|
||||
status: this.status,
|
||||
ref: this.ref,
|
||||
tag: this.tag,
|
||||
web_url: this.webUrl,
|
||||
created_at: this.createdAt,
|
||||
started_at: this.startedAt,
|
||||
finished_at: this.finishedAt,
|
||||
duration: this.duration,
|
||||
queued_duration: this.queuedDuration,
|
||||
coverage: this.coverage,
|
||||
allow_failure: this.allowFailure,
|
||||
failure_reason: this.failureReason,
|
||||
pipeline: { id: 0, project_id: 0, ref: '', sha: '', status: '' },
|
||||
user: { id: 0, username: '', name: '', email: '', avatar_url: '', web_url: '', state: '' },
|
||||
runner: { id: 0, description: '', active: false, is_shared: false },
|
||||
artifacts: [],
|
||||
artifacts_expire_at: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
122
ts/gitlab.classes.pipeline.ts
Normal file
122
ts/gitlab.classes.pipeline.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import type { GitLabClient } from './gitlab.classes.gitlabclient.js';
|
||||
import type { IGitLabPipeline, IJobListOptions } from './gitlab.interfaces.js';
|
||||
import { GitLabJob } from './gitlab.classes.job.js';
|
||||
import { GitLabPipelineVariable } from './gitlab.classes.variable.js';
|
||||
import { GitLabTestReport } from './gitlab.classes.testreport.js';
|
||||
import { autoPaginate } from './gitlab.helpers.js';
|
||||
|
||||
export class GitLabPipeline {
|
||||
// Raw data
|
||||
public readonly id: number;
|
||||
public readonly iid: number;
|
||||
public readonly projectId: number;
|
||||
public readonly status: string;
|
||||
public readonly ref: string;
|
||||
public readonly sha: string;
|
||||
public readonly webUrl: string;
|
||||
public readonly duration: number;
|
||||
public readonly queuedDuration: number;
|
||||
public readonly createdAt: string;
|
||||
public readonly updatedAt: string;
|
||||
public readonly startedAt: string;
|
||||
public readonly finishedAt: string;
|
||||
public readonly source: string;
|
||||
public readonly tag: boolean;
|
||||
|
||||
/** @internal */
|
||||
constructor(
|
||||
private client: GitLabClient,
|
||||
raw: IGitLabPipeline,
|
||||
) {
|
||||
this.id = raw.id;
|
||||
this.iid = raw.iid || 0;
|
||||
this.projectId = raw.project_id || 0;
|
||||
this.status = raw.status || '';
|
||||
this.ref = raw.ref || '';
|
||||
this.sha = raw.sha || '';
|
||||
this.webUrl = raw.web_url || '';
|
||||
this.duration = raw.duration || 0;
|
||||
this.queuedDuration = raw.queued_duration || 0;
|
||||
this.createdAt = raw.created_at || '';
|
||||
this.updatedAt = raw.updated_at || '';
|
||||
this.startedAt = raw.started_at || '';
|
||||
this.finishedAt = raw.finished_at || '';
|
||||
this.source = raw.source || '';
|
||||
this.tag = raw.tag ?? false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Jobs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getJobs(opts?: IJobListOptions): Promise<GitLabJob[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.client.requestGetPipelineJobs(this.projectId, this.id, { ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(jobs => jobs.map(j => new GitLabJob(this.client, this.projectId, j)));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Variables & Test Report
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getVariables(): Promise<GitLabPipelineVariable[]> {
|
||||
const vars = await this.client.requestGetPipelineVariables(this.projectId, this.id);
|
||||
return vars.map(v => new GitLabPipelineVariable(v));
|
||||
}
|
||||
|
||||
async getTestReport(): Promise<GitLabTestReport> {
|
||||
const raw = await this.client.requestGetPipelineTestReport(this.projectId, this.id);
|
||||
return new GitLabTestReport(raw);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Actions
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async retry(): Promise<GitLabPipeline> {
|
||||
const raw = await this.client.requestRetryPipeline(this.projectId, this.id);
|
||||
return new GitLabPipeline(this.client, raw);
|
||||
}
|
||||
|
||||
async cancel(): Promise<GitLabPipeline> {
|
||||
const raw = await this.client.requestCancelPipeline(this.projectId, this.id);
|
||||
return new GitLabPipeline(this.client, raw);
|
||||
}
|
||||
|
||||
async delete(): Promise<void> {
|
||||
await this.client.requestDeletePipeline(this.projectId, this.id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serialization
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
toJSON(): IGitLabPipeline {
|
||||
return {
|
||||
id: this.id,
|
||||
iid: this.iid,
|
||||
project_id: this.projectId,
|
||||
status: this.status,
|
||||
ref: this.ref,
|
||||
sha: this.sha,
|
||||
before_sha: '',
|
||||
tag: this.tag,
|
||||
web_url: this.webUrl,
|
||||
duration: this.duration,
|
||||
queued_duration: this.queuedDuration,
|
||||
created_at: this.createdAt,
|
||||
updated_at: this.updatedAt,
|
||||
started_at: this.startedAt,
|
||||
finished_at: this.finishedAt,
|
||||
source: this.source,
|
||||
coverage: '',
|
||||
user: { id: 0, username: '', name: '', email: '', avatar_url: '', web_url: '', state: '' },
|
||||
detailed_status: {
|
||||
icon: '', text: '', label: '', group: '', tooltip: '',
|
||||
has_details: false, details_path: '', favicon: '',
|
||||
},
|
||||
yaml_errors: '',
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,177 @@
|
||||
import { GitlabGroup } from './gitlab.classes.group';
|
||||
import * as plugins from './gitlab.plugins';
|
||||
import type { GitLabClient } from './gitlab.classes.gitlabclient.js';
|
||||
import type { IGitLabProject, IListOptions, IVariableOptions, IPipelineListOptions } from './gitlab.interfaces.js';
|
||||
import { GitLabBranch } from './gitlab.classes.branch.js';
|
||||
import { GitLabTag } from './gitlab.classes.tag.js';
|
||||
import { GitLabProtectedBranch } from './gitlab.classes.protectedbranch.js';
|
||||
import { GitLabVariable } from './gitlab.classes.variable.js';
|
||||
import { GitLabPipeline } from './gitlab.classes.pipeline.js';
|
||||
import { autoPaginate } from './gitlab.helpers.js';
|
||||
|
||||
export class GitlabProject {
|
||||
// STATIC
|
||||
public static async getProjectsForGroup(gitlabGroupArg: GitlabGroup) {
|
||||
const response = await gitlabGroupArg.gitlabAccountRef.request('GET', `/groups/${gitlabGroupArg.data.id}/projects`, {
|
||||
per_page: '100'
|
||||
export class GitLabProject {
|
||||
// Raw data
|
||||
public readonly id: number;
|
||||
public readonly name: string;
|
||||
public readonly fullPath: string;
|
||||
public readonly description: string;
|
||||
public readonly defaultBranch: string;
|
||||
public readonly webUrl: string;
|
||||
public readonly visibility: string;
|
||||
public readonly topics: string[];
|
||||
public readonly lastActivityAt: string;
|
||||
|
||||
/** @internal */
|
||||
constructor(
|
||||
private client: GitLabClient,
|
||||
raw: IGitLabProject,
|
||||
) {
|
||||
this.id = raw.id;
|
||||
this.name = raw.name || '';
|
||||
this.fullPath = raw.path_with_namespace || '';
|
||||
this.description = raw.description || '';
|
||||
this.defaultBranch = raw.default_branch || 'main';
|
||||
this.webUrl = raw.web_url || '';
|
||||
this.visibility = raw.visibility || 'private';
|
||||
this.topics = raw.topics || [];
|
||||
this.lastActivityAt = raw.last_activity_at || '';
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Branches & Tags
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getBranches(opts?: IListOptions): Promise<GitLabBranch[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.client.requestGetRepoBranches(this.id, { ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(branches => branches.map(b => new GitLabBranch(b)));
|
||||
}
|
||||
|
||||
async getTags(opts?: IListOptions): Promise<GitLabTag[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.client.requestGetRepoTags(this.id, { ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(tags => tags.map(t => new GitLabTag(t)));
|
||||
}
|
||||
|
||||
async getProtectedBranches(): Promise<GitLabProtectedBranch[]> {
|
||||
const branches = await this.client.requestGetProtectedBranches(this.id);
|
||||
return branches.map(b => new GitLabProtectedBranch(b));
|
||||
}
|
||||
|
||||
async unprotectBranch(branchName: string): Promise<void> {
|
||||
await this.client.requestUnprotectBranch(this.id, branchName);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Variables (CI/CD)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getVariables(): Promise<GitLabVariable[]> {
|
||||
const vars = await this.client.requestGetProjectVariables(this.id);
|
||||
return vars.map(v => new GitLabVariable(v));
|
||||
}
|
||||
|
||||
async createVariable(key: string, value: string, opts?: IVariableOptions): Promise<GitLabVariable> {
|
||||
const raw = await this.client.requestCreateProjectVariable(this.id, key, value, opts);
|
||||
return new GitLabVariable(raw);
|
||||
}
|
||||
|
||||
async updateVariable(key: string, value: string, opts?: IVariableOptions): Promise<GitLabVariable> {
|
||||
const raw = await this.client.requestUpdateProjectVariable(this.id, key, value, opts);
|
||||
return new GitLabVariable(raw);
|
||||
}
|
||||
|
||||
async deleteVariable(key: string): Promise<void> {
|
||||
await this.client.requestDeleteProjectVariable(this.id, key);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pipelines
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async getPipelines(opts?: IPipelineListOptions): Promise<GitLabPipeline[]> {
|
||||
return autoPaginate(
|
||||
(page, perPage) => this.client.requestGetPipelines(this.id, { ...opts, page, perPage }),
|
||||
opts,
|
||||
).then(pipelines => pipelines.map(p => new GitLabPipeline(this.client, p)));
|
||||
}
|
||||
|
||||
async triggerPipeline(
|
||||
ref: string,
|
||||
variables?: { key: string; value: string; variable_type?: string }[],
|
||||
): Promise<GitLabPipeline> {
|
||||
const raw = await this.client.requestTriggerPipeline(this.id, ref, variables);
|
||||
return new GitLabPipeline(this.client, raw);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mutation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Update project properties.
|
||||
*/
|
||||
async update(data: {
|
||||
name?: string;
|
||||
path?: string;
|
||||
description?: string;
|
||||
defaultBranch?: string;
|
||||
visibility?: string;
|
||||
topics?: string[];
|
||||
}): Promise<void> {
|
||||
await this.client.requestUpdateProject(this.id, {
|
||||
name: data.name,
|
||||
path: data.path,
|
||||
description: data.description,
|
||||
default_branch: data.defaultBranch,
|
||||
visibility: data.visibility,
|
||||
topics: data.topics,
|
||||
});
|
||||
console.log(response);
|
||||
for (const projectData of response) {
|
||||
console.log(projectData);
|
||||
}
|
||||
console.log(response.length);
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
gitlabGroupRef: GitlabGroup;
|
||||
data: any;
|
||||
|
||||
constructor(dataArg: any, gitlabGroupRefArg: GitlabGroup) {
|
||||
this.data = dataArg;
|
||||
this.gitlabGroupRef = gitlabGroupRefArg;
|
||||
/**
|
||||
* Upload an avatar image for this project (multipart FormData).
|
||||
*/
|
||||
async setAvatar(imageData: Uint8Array, filename: string): Promise<void> {
|
||||
await this.client.requestSetProjectAvatar(this.id, imageData, filename);
|
||||
}
|
||||
|
||||
public async getReadmeAsMarkdown() {};
|
||||
/**
|
||||
* Remove the project's avatar.
|
||||
*/
|
||||
async deleteAvatar(): Promise<void> {
|
||||
await this.client.requestUpdateProject(this.id, { avatar: '' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfer this project to a different namespace.
|
||||
*/
|
||||
async transfer(namespaceId: number): Promise<void> {
|
||||
await this.client.requestTransferProject(this.id, namespaceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete this project.
|
||||
*/
|
||||
async delete(): Promise<void> {
|
||||
await this.client.requestDeleteProject(this.id);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serialization
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
toJSON(): IGitLabProject {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
path_with_namespace: this.fullPath,
|
||||
description: this.description,
|
||||
default_branch: this.defaultBranch,
|
||||
web_url: this.webUrl,
|
||||
visibility: this.visibility,
|
||||
topics: this.topics,
|
||||
last_activity_at: this.lastActivityAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
21
ts/gitlab.classes.protectedbranch.ts
Normal file
21
ts/gitlab.classes.protectedbranch.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { IGitLabProtectedBranch } from './gitlab.interfaces.js';
|
||||
|
||||
export class GitLabProtectedBranch {
|
||||
public readonly id: number;
|
||||
public readonly name: string;
|
||||
public readonly allowForcePush: boolean;
|
||||
|
||||
constructor(raw: IGitLabProtectedBranch) {
|
||||
this.id = raw.id;
|
||||
this.name = raw.name || '';
|
||||
this.allowForcePush = raw.allow_force_push ?? false;
|
||||
}
|
||||
|
||||
toJSON(): IGitLabProtectedBranch {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
allow_force_push: this.allowForcePush,
|
||||
};
|
||||
}
|
||||
}
|
||||
18
ts/gitlab.classes.tag.ts
Normal file
18
ts/gitlab.classes.tag.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import type { IGitLabTag } from './gitlab.interfaces.js';
|
||||
|
||||
export class GitLabTag {
|
||||
public readonly name: string;
|
||||
public readonly commitSha: string;
|
||||
|
||||
constructor(raw: IGitLabTag) {
|
||||
this.name = raw.name || '';
|
||||
this.commitSha = raw.commit?.id || '';
|
||||
}
|
||||
|
||||
toJSON(): IGitLabTag {
|
||||
return {
|
||||
name: this.name,
|
||||
commit: { id: this.commitSha },
|
||||
};
|
||||
}
|
||||
}
|
||||
97
ts/gitlab.classes.testreport.ts
Normal file
97
ts/gitlab.classes.testreport.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import type { IGitLabTestReport, IGitLabTestSuite, IGitLabTestCase } from './gitlab.interfaces.js';
|
||||
|
||||
export class GitLabTestCase {
|
||||
public readonly status: string;
|
||||
public readonly name: string;
|
||||
public readonly classname: string;
|
||||
public readonly executionTime: number;
|
||||
public readonly systemOutput: string;
|
||||
public readonly stackTrace: string;
|
||||
|
||||
constructor(raw: IGitLabTestCase) {
|
||||
this.status = raw.status || '';
|
||||
this.name = raw.name || '';
|
||||
this.classname = raw.classname || '';
|
||||
this.executionTime = raw.execution_time || 0;
|
||||
this.systemOutput = raw.system_output || '';
|
||||
this.stackTrace = raw.stack_trace || '';
|
||||
}
|
||||
|
||||
toJSON(): IGitLabTestCase {
|
||||
return {
|
||||
status: this.status,
|
||||
name: this.name,
|
||||
classname: this.classname,
|
||||
execution_time: this.executionTime,
|
||||
system_output: this.systemOutput,
|
||||
stack_trace: this.stackTrace,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GitLabTestSuite {
|
||||
public readonly name: string;
|
||||
public readonly totalTime: number;
|
||||
public readonly totalCount: number;
|
||||
public readonly successCount: number;
|
||||
public readonly failedCount: number;
|
||||
public readonly skippedCount: number;
|
||||
public readonly errorCount: number;
|
||||
public readonly testCases: GitLabTestCase[];
|
||||
|
||||
constructor(raw: IGitLabTestSuite) {
|
||||
this.name = raw.name || '';
|
||||
this.totalTime = raw.total_time || 0;
|
||||
this.totalCount = raw.total_count || 0;
|
||||
this.successCount = raw.success_count || 0;
|
||||
this.failedCount = raw.failed_count || 0;
|
||||
this.skippedCount = raw.skipped_count || 0;
|
||||
this.errorCount = raw.error_count || 0;
|
||||
this.testCases = (raw.test_cases || []).map(tc => new GitLabTestCase(tc));
|
||||
}
|
||||
|
||||
toJSON(): IGitLabTestSuite {
|
||||
return {
|
||||
name: this.name,
|
||||
total_time: this.totalTime,
|
||||
total_count: this.totalCount,
|
||||
success_count: this.successCount,
|
||||
failed_count: this.failedCount,
|
||||
skipped_count: this.skippedCount,
|
||||
error_count: this.errorCount,
|
||||
test_cases: this.testCases.map(tc => tc.toJSON()),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GitLabTestReport {
|
||||
public readonly totalTime: number;
|
||||
public readonly totalCount: number;
|
||||
public readonly successCount: number;
|
||||
public readonly failedCount: number;
|
||||
public readonly skippedCount: number;
|
||||
public readonly errorCount: number;
|
||||
public readonly testSuites: GitLabTestSuite[];
|
||||
|
||||
constructor(raw: IGitLabTestReport) {
|
||||
this.totalTime = raw.total_time || 0;
|
||||
this.totalCount = raw.total_count || 0;
|
||||
this.successCount = raw.success_count || 0;
|
||||
this.failedCount = raw.failed_count || 0;
|
||||
this.skippedCount = raw.skipped_count || 0;
|
||||
this.errorCount = raw.error_count || 0;
|
||||
this.testSuites = (raw.test_suites || []).map(ts => new GitLabTestSuite(ts));
|
||||
}
|
||||
|
||||
toJSON(): IGitLabTestReport {
|
||||
return {
|
||||
total_time: this.totalTime,
|
||||
total_count: this.totalCount,
|
||||
success_count: this.successCount,
|
||||
failed_count: this.failedCount,
|
||||
skipped_count: this.skippedCount,
|
||||
error_count: this.errorCount,
|
||||
test_suites: this.testSuites.map(ts => ts.toJSON()),
|
||||
};
|
||||
}
|
||||
}
|
||||
50
ts/gitlab.classes.variable.ts
Normal file
50
ts/gitlab.classes.variable.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { IGitLabVariable, IGitLabPipelineVariable } from './gitlab.interfaces.js';
|
||||
|
||||
export class GitLabVariable {
|
||||
public readonly key: string;
|
||||
public readonly value: string;
|
||||
public readonly variableType: string;
|
||||
public readonly protected: boolean;
|
||||
public readonly masked: boolean;
|
||||
public readonly environmentScope: string;
|
||||
|
||||
constructor(raw: IGitLabVariable) {
|
||||
this.key = raw.key || '';
|
||||
this.value = raw.value || '';
|
||||
this.variableType = raw.variable_type || 'env_var';
|
||||
this.protected = raw.protected ?? false;
|
||||
this.masked = raw.masked ?? false;
|
||||
this.environmentScope = raw.environment_scope || '*';
|
||||
}
|
||||
|
||||
toJSON(): IGitLabVariable {
|
||||
return {
|
||||
key: this.key,
|
||||
value: this.value,
|
||||
variable_type: this.variableType,
|
||||
protected: this.protected,
|
||||
masked: this.masked,
|
||||
environment_scope: this.environmentScope,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class GitLabPipelineVariable {
|
||||
public readonly key: string;
|
||||
public readonly value: string;
|
||||
public readonly variableType: string;
|
||||
|
||||
constructor(raw: IGitLabPipelineVariable) {
|
||||
this.key = raw.key || '';
|
||||
this.value = raw.value || '';
|
||||
this.variableType = raw.variable_type || 'env_var';
|
||||
}
|
||||
|
||||
toJSON(): IGitLabPipelineVariable {
|
||||
return {
|
||||
key: this.key,
|
||||
value: this.value,
|
||||
variable_type: this.variableType,
|
||||
};
|
||||
}
|
||||
}
|
||||
26
ts/gitlab.helpers.ts
Normal file
26
ts/gitlab.helpers.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Auto-paginate a list endpoint.
|
||||
* If opts includes a specific page, returns just that page (no auto-pagination).
|
||||
*/
|
||||
export async function autoPaginate<T>(
|
||||
fetchPage: (page: number, perPage: number) => Promise<T[]>,
|
||||
opts?: { page?: number; perPage?: number },
|
||||
): Promise<T[]> {
|
||||
const perPage = opts?.perPage || 50;
|
||||
|
||||
// If caller requests a specific page, return just that page
|
||||
if (opts?.page) {
|
||||
return fetchPage(opts.page, perPage);
|
||||
}
|
||||
|
||||
// Otherwise auto-paginate through all pages
|
||||
const all: T[] = [];
|
||||
let page = 1;
|
||||
while (true) {
|
||||
const items = await fetchPage(page, perPage);
|
||||
all.push(...items);
|
||||
if (items.length < perPage) break;
|
||||
page++;
|
||||
}
|
||||
return all;
|
||||
}
|
||||
247
ts/gitlab.interfaces.ts
Normal file
247
ts/gitlab.interfaces.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// Common
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface ITestConnectionResult {
|
||||
ok: boolean;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface IListOptions {
|
||||
search?: string;
|
||||
page?: number;
|
||||
perPage?: number;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pipeline / Job list options
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IPipelineListOptions extends IListOptions {
|
||||
/** Filter by pipeline status */
|
||||
status?: string;
|
||||
/** Filter by branch or tag ref */
|
||||
ref?: string;
|
||||
/** Filter by trigger source (push, web, trigger, schedule, api, external, pipeline, chat, merge_request_event, …) */
|
||||
source?: string;
|
||||
/** Filter by scope (running, pending, finished, branches, tags) */
|
||||
scope?: string;
|
||||
/** Filter by the user who triggered the pipeline */
|
||||
username?: string;
|
||||
/** Return pipelines updated after this ISO 8601 date */
|
||||
updatedAfter?: string;
|
||||
/** Return pipelines updated before this ISO 8601 date */
|
||||
updatedBefore?: string;
|
||||
/** Order by field (id, status, ref, updated_at, user_id). Default: id */
|
||||
orderBy?: string;
|
||||
/** Sort direction (asc, desc). Default: desc */
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
export interface IJobListOptions extends IListOptions {
|
||||
/** Filter by job scope(s) */
|
||||
scope?: string[];
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Users
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabUser {
|
||||
id: number;
|
||||
username: string;
|
||||
name: string;
|
||||
email: string;
|
||||
avatar_url: string;
|
||||
web_url: string;
|
||||
state: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Projects
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabProject {
|
||||
id: number;
|
||||
name: string;
|
||||
path_with_namespace: string;
|
||||
description: string;
|
||||
default_branch: string;
|
||||
web_url: string;
|
||||
visibility: string;
|
||||
topics: string[];
|
||||
last_activity_at: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Groups
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabGroup {
|
||||
id: number;
|
||||
name: string;
|
||||
full_path: string;
|
||||
description: string;
|
||||
web_url: string;
|
||||
visibility: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Variables
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
variable_type: string;
|
||||
protected: boolean;
|
||||
masked: boolean;
|
||||
environment_scope: string;
|
||||
}
|
||||
|
||||
export interface IVariableOptions {
|
||||
protected?: boolean;
|
||||
masked?: boolean;
|
||||
environment_scope?: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Protected Branches
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabProtectedBranch {
|
||||
id: number;
|
||||
name: string;
|
||||
allow_force_push: boolean;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pipelines
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabPipeline {
|
||||
id: number;
|
||||
iid: number;
|
||||
project_id: number;
|
||||
status: string;
|
||||
ref: string;
|
||||
sha: string;
|
||||
before_sha: string;
|
||||
tag: boolean;
|
||||
web_url: string;
|
||||
duration: number;
|
||||
queued_duration: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
started_at: string;
|
||||
finished_at: string;
|
||||
source: string;
|
||||
coverage: string;
|
||||
user: IGitLabUser;
|
||||
detailed_status: {
|
||||
icon: string;
|
||||
text: string;
|
||||
label: string;
|
||||
group: string;
|
||||
tooltip: string;
|
||||
has_details: boolean;
|
||||
details_path: string;
|
||||
favicon: string;
|
||||
};
|
||||
yaml_errors: string;
|
||||
}
|
||||
|
||||
export interface IGitLabPipelineVariable {
|
||||
key: string;
|
||||
value: string;
|
||||
variable_type: string;
|
||||
}
|
||||
|
||||
export interface IGitLabTestReport {
|
||||
total_time: number;
|
||||
total_count: number;
|
||||
success_count: number;
|
||||
failed_count: number;
|
||||
skipped_count: number;
|
||||
error_count: number;
|
||||
test_suites: IGitLabTestSuite[];
|
||||
}
|
||||
|
||||
export interface IGitLabTestSuite {
|
||||
name: string;
|
||||
total_time: number;
|
||||
total_count: number;
|
||||
success_count: number;
|
||||
failed_count: number;
|
||||
skipped_count: number;
|
||||
error_count: number;
|
||||
test_cases: IGitLabTestCase[];
|
||||
}
|
||||
|
||||
export interface IGitLabTestCase {
|
||||
status: string;
|
||||
name: string;
|
||||
classname: string;
|
||||
execution_time: number;
|
||||
system_output: string;
|
||||
stack_trace: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Jobs
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabJob {
|
||||
id: number;
|
||||
name: string;
|
||||
stage: string;
|
||||
status: string;
|
||||
ref: string;
|
||||
tag: boolean;
|
||||
web_url: string;
|
||||
created_at: string;
|
||||
started_at: string;
|
||||
finished_at: string;
|
||||
duration: number;
|
||||
queued_duration: number;
|
||||
coverage: number;
|
||||
allow_failure: boolean;
|
||||
failure_reason: string;
|
||||
pipeline: {
|
||||
id: number;
|
||||
project_id: number;
|
||||
ref: string;
|
||||
sha: string;
|
||||
status: string;
|
||||
};
|
||||
user: IGitLabUser;
|
||||
runner: {
|
||||
id: number;
|
||||
description: string;
|
||||
active: boolean;
|
||||
is_shared: boolean;
|
||||
};
|
||||
artifacts: {
|
||||
filename: string;
|
||||
size: number;
|
||||
}[];
|
||||
artifacts_expire_at: string;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Branches & Tags
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export interface IGitLabBranch {
|
||||
name: string;
|
||||
commit: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IGitLabTag {
|
||||
name: string;
|
||||
commit: {
|
||||
id: string;
|
||||
};
|
||||
}
|
||||
3
ts/gitlab.logging.ts
Normal file
3
ts/gitlab.logging.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as plugins from './gitlab.plugins.js';
|
||||
|
||||
export const logger = new plugins.smartlog.ConsoleLog();
|
||||
@@ -1,8 +1,4 @@
|
||||
// pushrocks scope
|
||||
import * as smartrequest from '@pushrocks/smartrequest';
|
||||
import * as smarturl from '@pushrocks/smarturl';
|
||||
import * as smartlog from '@push.rocks/smartlog';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
|
||||
export {
|
||||
smartrequest,
|
||||
smarturl
|
||||
};
|
||||
export { smartlog, smartrequest };
|
||||
|
||||
43
ts/index.ts
43
ts/index.ts
@@ -1,2 +1,41 @@
|
||||
export * from './gitlab.classes.group';
|
||||
export * from './gitlab.classes.account';
|
||||
// Main client
|
||||
export { GitLabClient } from './gitlab.classes.gitlabclient.js';
|
||||
|
||||
// Domain classes
|
||||
export { GitLabGroup } from './gitlab.classes.group.js';
|
||||
export { GitLabProject } from './gitlab.classes.project.js';
|
||||
export { GitLabPipeline } from './gitlab.classes.pipeline.js';
|
||||
export { GitLabJob } from './gitlab.classes.job.js';
|
||||
export { GitLabBranch } from './gitlab.classes.branch.js';
|
||||
export { GitLabTag } from './gitlab.classes.tag.js';
|
||||
export { GitLabProtectedBranch } from './gitlab.classes.protectedbranch.js';
|
||||
export { GitLabVariable, GitLabPipelineVariable } from './gitlab.classes.variable.js';
|
||||
export { GitLabTestReport, GitLabTestSuite, GitLabTestCase } from './gitlab.classes.testreport.js';
|
||||
|
||||
// Helpers
|
||||
export { autoPaginate } from './gitlab.helpers.js';
|
||||
|
||||
// Interfaces (raw API types)
|
||||
export type {
|
||||
IGitLabUser,
|
||||
IGitLabProject,
|
||||
IGitLabGroup,
|
||||
IGitLabVariable,
|
||||
IGitLabProtectedBranch,
|
||||
IGitLabBranch,
|
||||
IGitLabTag,
|
||||
IVariableOptions,
|
||||
IGitLabPipeline,
|
||||
IGitLabPipelineVariable,
|
||||
IGitLabTestReport,
|
||||
IGitLabTestSuite,
|
||||
IGitLabTestCase,
|
||||
IGitLabJob,
|
||||
ITestConnectionResult,
|
||||
IListOptions,
|
||||
IPipelineListOptions,
|
||||
IJobListOptions,
|
||||
} from './gitlab.interfaces.js';
|
||||
|
||||
// Commit info
|
||||
export { commitinfo } from './00_commitinfo_data.js';
|
||||
|
||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {}
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
17
tslint.json
17
tslint.json
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"ordered-imports": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"member-ordering": {
|
||||
"options":{
|
||||
"order": [
|
||||
"static-method"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
||||
Reference in New Issue
Block a user