26 Commits

Author SHA1 Message Date
016eaf1b91 v2.2.0 2026-02-28 11:13:53 +00:00
cbb9f364ae feat(gitlabclient): add deleteProject method to delete a project using GitLab API DELETE /api/v4/projects/:id 2026-02-28 11:13:53 +00:00
ef688115d6 v2.1.0 2026-02-28 10:23:20 +00:00
3aaa0f7e68 feat(gitlab): add group- and project-management methods to GitLab client 2026-02-28 10:23:20 +00:00
1cd7c62431 v2.0.3 2026-02-24 12:59:35 +00:00
8f85104136 fix(): no changes detected; nothing to commit 2026-02-24 12:59:35 +00:00
c8059fb1c0 fix(test): await async getEnvVarOnDemand calls in test setup 2026-02-24 12:59:11 +00:00
413c4be172 docs(core): add comprehensive readme with full API documentation 2026-02-24 12:57:27 +00:00
e7cfd5dec1 v2.0.2 2026-02-24 12:55:39 +00:00
e002e90bd0 fix(): no changes detected in diff; no version bump required 2026-02-24 12:55:39 +00:00
f460b92c3a fix(core): add verdaccio as primary release registry 2026-02-24 12:55:20 +00:00
65e5575494 v2.0.1 2026-02-24 12:54:50 +00:00
4da6b14dc2 fix(): no changes detected; no version bump required 2026-02-24 12:54:50 +00:00
95c5870252 fix(core): add npm release registry configuration 2026-02-24 12:53:39 +00:00
b0f56f5948 BREAKING CHANGE(gitlab): rename to @apiclient.xyz/gitlab v2.0.0 with new GitLabClient API
Replaces legacy @mojoio/gitlab with modern ESM TypeScript client supporting projects, groups, CI/CD variables, and pipelines.
2026-02-24 12:50:18 +00:00
cc00a78e2c 1.0.10 2021-10-05 15:29:47 +02:00
cf7e69e74a fix(core): update 2021-10-05 15:29:47 +02:00
5cc3aeb27c 1.0.9 2021-10-04 22:02:48 +02:00
098a9c64c6 fix(core): update 2021-10-04 22:02:47 +02:00
1fd2bb8e01 1.0.8 2021-05-17 00:07:52 +00:00
ca475fbfab 1.0.7 2021-05-16 23:59:47 +00:00
c8dc59cdf2 fix(core): update 2021-05-16 23:59:47 +00:00
217724d836 1.0.6 2021-05-16 23:59:01 +00:00
b0efd2671c fix(core): update 2021-05-16 23:59:01 +00:00
b80e63dc65 1.0.5 2021-05-16 23:50:57 +00:00
7765ff77f9 fix(core): update 2021-05-16 23:50:56 +00:00
22 changed files with 10906 additions and 11921 deletions

19
.gitignore vendored
View File

@@ -1,4 +1,19 @@
node_modules/
.nogit/
# artifacts
coverage/
public/
pages/
# installs
node_modules/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_*/
#------# custom

View File

@@ -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

55
changelog.md Normal file
View File

@@ -0,0 +1,55 @@
# Changelog
## 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.41.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
View 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.

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +1,55 @@
{
"name": "@mojoio/gitlab",
"version": "1.0.4",
"description": "api abstraction package for gitlab",
"name": "@apiclient.xyz/gitlab",
"version": "2.2.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

File diff suppressed because it is too large Load Diff

311
readme.md
View File

@@ -1,29 +1,296 @@
# glab
api abstraction package for gitlab
# @apiclient.xyz/gitlab
## Availabililty
[![npm](https://mojoio.gitlab.io/assets/repo-button-npm.svg)](https://www.npmjs.com/package/glab)
[![git](https://mojoio.gitlab.io/assets/repo-button-git.svg)](https://GitLab.com/mojoio/glab)
[![git](https://mojoio.gitlab.io/assets/repo-button-mirror.svg)](https://github.com/mojoio/glab)
[![docs](https://mojoio.gitlab.io/assets/repo-button-docs.svg)](https://mojoio.gitlab.io/glab/)
A TypeScript client for the GitLab API, providing easy access to projects, groups, CI/CD variables, and pipelines.
## Status for master
[![build status](https://GitLab.com/mojoio/glab/badges/master/build.svg)](https://GitLab.com/mojoio/glab/commits/master)
[![coverage report](https://GitLab.com/mojoio/glab/badges/master/coverage.svg)](https://GitLab.com/mojoio/glab/commits/master)
[![npm downloads per month](https://img.shields.io/npm/dm/glab.svg)](https://www.npmjs.com/package/glab)
[![Dependency Status](https://david-dm.org/mojoio/glab.svg)](https://david-dm.org/mojoio/glab)
[![bitHound Dependencies](https://www.bithound.io/github/mojoio/glab/badges/dependencies.svg)](https://www.bithound.io/github/mojoio/glab/master/dependencies/npm)
[![bitHound Code](https://www.bithound.io/github/mojoio/glab/badges/code.svg)](https://www.bithound.io/github/mojoio/glab)
[![TypeScript](https://img.shields.io/badge/TypeScript-2.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%206.x.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
## Install
```bash
npm install @apiclient.xyz/gitlab
```
## Usage
Use TypeScript for best in class instellisense.
For further information read the linked docs at the top of this README.
All examples below use ESM imports and async/await.
> MIT licensed | **&copy;** [Lossless GmbH](https://lossless.gmbh)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
### Creating a Client
[![repo-footer](https://mojoio.gitlab.io/assets/repo-footer.svg)](https://mojo.io)
```typescript
import { GitLabClient } from '@apiclient.xyz/gitlab';
const client = new GitLabClient('https://gitlab.example.com', 'your-private-token');
```
The constructor accepts the base URL of your GitLab instance and a personal access token (or project/group token) used for authentication via the `PRIVATE-TOKEN` header.
### Testing the Connection
```typescript
const result = await client.testConnection();
if (result.ok) {
console.log('Connected successfully.');
} else {
console.error('Connection failed:', result.error);
}
```
### Listing Projects
Retrieve projects you are a member of, ordered by most recently updated.
```typescript
// First page, default 50 per page
const projects = await client.getProjects();
// Search with pagination
const filtered = await client.getProjects({
search: 'my-service',
page: 1,
perPage: 20,
});
for (const project of filtered) {
console.log(`${project.id} - ${project.path_with_namespace}`);
}
```
### Listing Groups
```typescript
const groups = await client.getGroups();
// Search groups by name
const matchingGroups = await client.getGroups({ search: 'platform' });
for (const group of matchingGroups) {
console.log(`${group.id} - ${group.full_path}`);
}
```
### Managing Project Variables
```typescript
const projectId = 42;
// List all variables
const vars = await client.getProjectVariables(projectId);
// Create a variable
await client.createProjectVariable(projectId, 'DATABASE_URL', 'postgres://...', {
protected: true,
masked: true,
environment_scope: 'production',
});
// Update a variable
await client.updateProjectVariable(projectId, 'DATABASE_URL', 'postgres://new-host/...', {
protected: true,
});
// Delete a variable
await client.deleteProjectVariable(projectId, 'DATABASE_URL');
```
### Managing Group Variables
The group variable API mirrors the project variable API.
```typescript
const groupId = 7;
// List all variables
const groupVars = await client.getGroupVariables(groupId);
// Create a variable
await client.createGroupVariable(groupId, 'NPM_TOKEN', 'tok-xxx', {
protected: false,
masked: true,
environment_scope: '*',
});
// Update a variable
await client.updateGroupVariable(groupId, 'NPM_TOKEN', 'tok-yyy', {
masked: true,
});
// Delete a variable
await client.deleteGroupVariable(groupId, 'NPM_TOKEN');
```
### Working with Pipelines
```typescript
const projectId = 42;
// List recent pipelines
const pipelines = await client.getPipelines(projectId, { page: 1, perPage: 10 });
for (const pipeline of pipelines) {
console.log(`Pipeline #${pipeline.id} [${pipeline.status}] on ${pipeline.ref}`);
}
// Get jobs for a specific pipeline
const jobs = await client.getPipelineJobs(projectId, pipelines[0].id);
for (const job of jobs) {
console.log(` Job "${job.name}" (${job.stage}): ${job.status}`);
}
// Read the raw log output of a job
const log = await client.getJobLog(projectId, jobs[0].id);
console.log(log);
// Retry a failed pipeline
await client.retryPipeline(projectId, pipelines[0].id);
// Cancel a running pipeline
await client.cancelPipeline(projectId, pipelines[0].id);
```
## API Reference
### `GitLabClient`
| Method | Signature | Returns | Description |
|---|---|---|---|
| `constructor` | `(baseUrl: string, token: string)` | `GitLabClient` | Create a new client instance. |
| `testConnection` | `()` | `Promise<ITestConnectionResult>` | Verify the token and connectivity. |
| `getProjects` | `(opts?: IListOptions)` | `Promise<IGitLabProject[]>` | List projects you are a member of. |
| `getGroups` | `(opts?: IListOptions)` | `Promise<IGitLabGroup[]>` | List accessible groups. |
| `getProjectVariables` | `(projectId: number \| string)` | `Promise<IGitLabVariable[]>` | List all CI/CD variables for a project. |
| `createProjectVariable` | `(projectId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Create a CI/CD variable on a project. |
| `updateProjectVariable` | `(projectId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Update an existing project variable. |
| `deleteProjectVariable` | `(projectId: number \| string, key: string)` | `Promise<void>` | Delete a project variable. |
| `getGroupVariables` | `(groupId: number \| string)` | `Promise<IGitLabVariable[]>` | List all CI/CD variables for a group. |
| `createGroupVariable` | `(groupId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Create a CI/CD variable on a group. |
| `updateGroupVariable` | `(groupId: number \| string, key: string, value: string, opts?: IVariableOptions)` | `Promise<IGitLabVariable>` | Update an existing group variable. |
| `deleteGroupVariable` | `(groupId: number \| string, key: string)` | `Promise<void>` | Delete a group variable. |
| `getPipelines` | `(projectId: number \| string, opts?: IListOptions)` | `Promise<IGitLabPipeline[]>` | List pipelines for a project, newest first. |
| `getPipelineJobs` | `(projectId: number \| string, pipelineId: number)` | `Promise<IGitLabJob[]>` | Get all jobs for a pipeline. |
| `getJobLog` | `(projectId: number \| string, jobId: number)` | `Promise<string>` | Retrieve the raw trace/log of a job. |
| `retryPipeline` | `(projectId: number \| string, pipelineId: number)` | `Promise<void>` | Retry all failed jobs in a pipeline. |
| `cancelPipeline` | `(projectId: number \| string, pipelineId: number)` | `Promise<void>` | Cancel a running pipeline. |
## Types
### `IListOptions`
Pagination and search options used by `getProjects`, `getGroups`, and `getPipelines`.
```typescript
interface IListOptions {
search?: string; // Filter results by keyword
page?: number; // Page number (default: 1)
perPage?: number; // Items per page (default: 50 for projects/groups, 30 for pipelines)
}
```
### `IVariableOptions`
Options when creating or updating CI/CD variables.
```typescript
interface IVariableOptions {
protected?: boolean; // Only expose to protected branches/tags (default: false)
masked?: boolean; // Mask the value in job logs (default: false)
environment_scope?: string; // Environment scope (default: '*' for all environments)
}
```
### `ITestConnectionResult`
```typescript
interface ITestConnectionResult {
ok: boolean;
error?: string; // Present when ok is false
}
```
### `IGitLabProject`
```typescript
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;
}
```
### `IGitLabGroup`
```typescript
interface IGitLabGroup {
id: number;
name: string;
full_path: string;
description: string;
web_url: string;
visibility: string;
}
```
### `IGitLabVariable`
```typescript
interface IGitLabVariable {
key: string;
value: string;
variable_type: string;
protected: boolean;
masked: boolean;
environment_scope: string;
}
```
### `IGitLabPipeline`
```typescript
interface IGitLabPipeline {
id: number;
project_id: number;
status: string;
ref: string;
sha: string;
web_url: string;
duration: number;
created_at: string;
source: string;
}
```
### `IGitLabJob`
```typescript
interface IGitLabJob {
id: number;
name: string;
stage: string;
status: string;
duration: number;
}
```
### `IGitLabUser`
Returned internally by `testConnection` to verify credentials.
```typescript
interface IGitLabUser {
id: number;
username: string;
name: string;
email: string;
avatar_url: string;
web_url: string;
state: string;
}
```
## License
MIT

34
test/test.node.ts Normal file
View 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();

View File

@@ -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
View File

@@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@apiclient.xyz/gitlab',
version: '2.2.0',
description: 'A TypeScript client for the GitLab API, providing easy access to projects, groups, CI/CD variables, and pipelines.'
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,382 @@
import * as plugins from './gitlab.plugins.js';
import { logger } from './gitlab.logging.js';
import type {
IGitLabUser,
IGitLabProject,
IGitLabGroup,
IGitLabVariable,
IVariableOptions,
IGitLabPipeline,
IGitLabJob,
ITestConnectionResult,
IListOptions,
} from './gitlab.interfaces.js';
export class GitLabClient {
private baseUrl: string;
private token: string;
constructor(baseUrl: string, token: string) {
// Remove trailing slash if present
this.baseUrl = baseUrl.replace(/\/+$/, '');
this.token = token;
}
// ---------------------------------------------------------------------------
// HTTP helpers
// ---------------------------------------------------------------------------
private 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;
}
}
private 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();
}
// ---------------------------------------------------------------------------
// 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) };
}
}
// ---------------------------------------------------------------------------
// Groups — scoped queries
// ---------------------------------------------------------------------------
/**
* Get a single group by its full path (e.g. "foss.global" or "foss.global/push.rocks")
*/
public async getGroupByPath(fullPath: string): Promise<IGitLabGroup> {
return this.request<IGitLabGroup>(
'GET',
`/api/v4/groups/${encodeURIComponent(fullPath)}`,
);
}
/**
* List projects within a group (includes subgroups when include_subgroups=true)
*/
public async getGroupProjects(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);
}
/**
* List all descendant groups (recursive subgroups) within a group
*/
public async getDescendantGroups(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);
}
/**
* Create a new group. Optionally nested under a parent group.
*/
public async createGroup(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);
}
/**
* Create a new project (repository).
*/
public async createProject(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 || '',
});
}
// ---------------------------------------------------------------------------
// Projects
// ---------------------------------------------------------------------------
public async getProjects(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);
}
// ---------------------------------------------------------------------------
// Groups
// ---------------------------------------------------------------------------
public async getGroups(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);
}
// ---------------------------------------------------------------------------
// Project Variables (CI/CD)
// ---------------------------------------------------------------------------
public async getProjectVariables(projectId: number | string): Promise<IGitLabVariable[]> {
return this.request<IGitLabVariable[]>(
'GET',
`/api/v4/projects/${encodeURIComponent(projectId)}/variables`,
);
}
public async createProjectVariable(
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 ?? '*',
},
);
}
public async updateProjectVariable(
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,
);
}
public async deleteProjectVariable(projectId: number | string, key: string): Promise<void> {
await this.request(
'DELETE',
`/api/v4/projects/${encodeURIComponent(projectId)}/variables/${encodeURIComponent(key)}`,
);
}
// ---------------------------------------------------------------------------
// Group Variables (CI/CD)
// ---------------------------------------------------------------------------
public async getGroupVariables(groupId: number | string): Promise<IGitLabVariable[]> {
return this.request<IGitLabVariable[]>(
'GET',
`/api/v4/groups/${encodeURIComponent(groupId)}/variables`,
);
}
public async createGroupVariable(
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 ?? '*',
},
);
}
public async updateGroupVariable(
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,
);
}
public async deleteGroupVariable(groupId: number | string, key: string): Promise<void> {
await this.request(
'DELETE',
`/api/v4/groups/${encodeURIComponent(groupId)}/variables/${encodeURIComponent(key)}`,
);
}
// ---------------------------------------------------------------------------
// Pipelines
// ---------------------------------------------------------------------------
public async getPipelines(projectId: number | string, opts?: IListOptions): Promise<IGitLabPipeline[]> {
const page = opts?.page || 1;
const perPage = opts?.perPage || 30;
return this.request<IGitLabPipeline[]>(
'GET',
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines?page=${page}&per_page=${perPage}&order_by=updated_at&sort=desc`,
);
}
public async getPipelineJobs(projectId: number | string, pipelineId: number): Promise<IGitLabJob[]> {
return this.request<IGitLabJob[]>(
'GET',
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/jobs`,
);
}
public async getJobLog(projectId: number | string, jobId: number): Promise<string> {
return this.requestText(
'GET',
`/api/v4/projects/${encodeURIComponent(projectId)}/jobs/${jobId}/trace`,
);
}
public async retryPipeline(projectId: number | string, pipelineId: number): Promise<void> {
await this.request(
'POST',
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/retry`,
);
}
public async cancelPipeline(projectId: number | string, pipelineId: number): Promise<void> {
await this.request(
'POST',
`/api/v4/projects/${encodeURIComponent(projectId)}/pipelines/${pipelineId}/cancel`,
);
}
// ---------------------------------------------------------------------------
// Project Deletion
// ---------------------------------------------------------------------------
public async deleteProject(projectId: number | string): Promise<void> {
await this.request(
'DELETE',
`/api/v4/projects/${encodeURIComponent(projectId)}`,
);
}
}

View File

@@ -1,54 +0,0 @@
import * as plugins from './gitlab.plugins';
import { GitlabAccount } from './gitlab.classes.account';
import { GitlabProject } from './gitlab.classes.project';
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 {
public static async getByName(nameArg: string, gitlabAccountArg: GitlabAccount) {
const response = await gitlabAccountArg.request('GET', '/groups', {
search: 'pushrocks',
});
// 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;
}
public async getProjects() {
return GitlabProject.getProjectsForGroup(this);
}
}

View File

@@ -1,27 +0,0 @@
import { GitlabGroup } from './gitlab.classes.group';
import * as plugins from './gitlab.plugins';
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'
});
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;
}
public async getReadmeAsMarkdown() {};
}

76
ts/gitlab.interfaces.ts Normal file
View File

@@ -0,0 +1,76 @@
export interface IGitLabUser {
id: number;
username: string;
name: string;
email: string;
avatar_url: string;
web_url: string;
state: string;
}
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;
}
export interface IGitLabGroup {
id: number;
name: string;
full_path: string;
description: string;
web_url: string;
visibility: string;
}
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;
}
export interface IGitLabPipeline {
id: number;
project_id: number;
status: string;
ref: string;
sha: string;
web_url: string;
duration: number;
created_at: string;
source: string;
}
export interface IGitLabJob {
id: number;
name: string;
stage: string;
status: string;
duration: number;
}
export interface ITestConnectionResult {
ok: boolean;
error?: string;
}
export interface IListOptions {
search?: string;
page?: number;
perPage?: number;
}

3
ts/gitlab.logging.ts Normal file
View File

@@ -0,0 +1,3 @@
import * as plugins from './gitlab.plugins.js';
export const logger = new plugins.smartlog.ConsoleLog();

View File

@@ -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 };

View File

@@ -1,2 +1,13 @@
export * from './gitlab.classes.group';
export * from './gitlab.classes.account';
export { GitLabClient } from './gitlab.classes.gitlabclient.js';
export type {
IGitLabUser,
IGitLabProject,
IGitLabGroup,
IGitLabVariable,
IVariableOptions,
IGitLabPipeline,
IGitLabJob,
ITestConnectionResult,
IListOptions,
} from './gitlab.interfaces.js';
export { commitinfo } from './00_commitinfo_data.js';

16
tsconfig.json Normal file
View 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"
]
}

View File

@@ -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"
}