@simplysf/simply-gitlab-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -0
- package/lib/auth.d.ts +9 -0
- package/lib/auth.js +26 -0
- package/lib/auth.js.map +1 -0
- package/lib/cicd.d.ts +29 -0
- package/lib/cicd.js +56 -0
- package/lib/cicd.js.map +1 -0
- package/lib/commits.d.ts +34 -0
- package/lib/commits.js +101 -0
- package/lib/commits.js.map +1 -0
- package/lib/config.d.ts +21 -0
- package/lib/config.js +76 -0
- package/lib/config.js.map +1 -0
- package/lib/env-file.d.ts +11 -0
- package/lib/env-file.js +98 -0
- package/lib/env-file.js.map +1 -0
- package/lib/errors.d.ts +31 -0
- package/lib/errors.js +66 -0
- package/lib/errors.js.map +1 -0
- package/lib/gitlab-client.d.ts +137 -0
- package/lib/gitlab-client.js +250 -0
- package/lib/gitlab-client.js.map +1 -0
- package/lib/http.d.ts +82 -0
- package/lib/http.js +285 -0
- package/lib/http.js.map +1 -0
- package/lib/index.d.ts +17 -0
- package/lib/index.js +46 -0
- package/lib/index.js.map +1 -0
- package/lib/json-input.d.ts +28 -0
- package/lib/json-input.js +84 -0
- package/lib/json-input.js.map +1 -0
- package/lib/merge-requests.d.ts +40 -0
- package/lib/merge-requests.js +89 -0
- package/lib/merge-requests.js.map +1 -0
- package/lib/output.d.ts +14 -0
- package/lib/output.js +65 -0
- package/lib/output.js.map +1 -0
- package/lib/redaction.d.ts +26 -0
- package/lib/redaction.js +71 -0
- package/lib/redaction.js.map +1 -0
- package/lib/repository-files.d.ts +50 -0
- package/lib/repository-files.js +70 -0
- package/lib/repository-files.js.map +1 -0
- package/lib/search.d.ts +20 -0
- package/lib/search.js +92 -0
- package/lib/search.js.map +1 -0
- package/lib/tables.d.ts +21 -0
- package/lib/tables.js +105 -0
- package/lib/tables.js.map +1 -0
- package/lib/testing.d.ts +16 -0
- package/lib/testing.js +63 -0
- package/lib/testing.js.map +1 -0
- package/lib/text.d.ts +10 -0
- package/lib/text.js +80 -0
- package/lib/text.js.map +1 -0
- package/lib/write-safety.d.ts +19 -0
- package/lib/write-safety.js +45 -0
- package/lib/write-safety.js.map +1 -0
- package/package.json +130 -0
package/lib/errors.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, SimplySF.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Base class for every error this CLI raises deliberately. Commands surface these through
|
|
18
|
+
* oclif's `this.error(message, { exit })` so users get formatted output rather than a stack
|
|
19
|
+
* trace for expected failure modes.
|
|
20
|
+
*/
|
|
21
|
+
export class CliError extends Error {
|
|
22
|
+
exitCode;
|
|
23
|
+
constructor(message, exitCode = 1) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'CliError';
|
|
26
|
+
this.exitCode = exitCode;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Missing, malformed, or self-contradictory configuration. */
|
|
30
|
+
export class ConfigError extends CliError {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message, 2);
|
|
33
|
+
this.name = 'ConfigError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The instance rejected our credentials. Carries the status because 401 and 403 call for
|
|
38
|
+
* different responses: bad credentials versus a valid account without the needed permission.
|
|
39
|
+
*/
|
|
40
|
+
export class AuthError extends CliError {
|
|
41
|
+
status;
|
|
42
|
+
constructor(message, status) {
|
|
43
|
+
super(message, 3);
|
|
44
|
+
this.name = 'AuthError';
|
|
45
|
+
this.status = status;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/** The instance never answered: timeout, DNS miss, refused connection, or an untrusted certificate. */
|
|
49
|
+
export class NetworkError extends CliError {
|
|
50
|
+
constructor(message) {
|
|
51
|
+
super(message, 1);
|
|
52
|
+
this.name = 'NetworkError';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Any other non-2xx response, carrying the status and whatever body came back. */
|
|
56
|
+
export class HttpError extends CliError {
|
|
57
|
+
status;
|
|
58
|
+
body;
|
|
59
|
+
constructor(message, status, body) {
|
|
60
|
+
super(message, 1);
|
|
61
|
+
this.name = 'HttpError';
|
|
62
|
+
this.status = status;
|
|
63
|
+
this.body = body;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;GAIG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACjB,QAAQ,CAAS;IAEjC,YAAmB,OAAe,EAAE,QAAQ,GAAG,CAAC;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;CACF;AAED,+DAA+D;AAC/D,MAAM,OAAO,WAAY,SAAQ,QAAQ;IACvC,YAAmB,OAAe;QAChC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;IAC5B,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,SAAU,SAAQ,QAAQ;IACrB,MAAM,CAAS;IAE/B,YAAmB,OAAe,EAAE,MAAc;QAChD,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;CACF;AAED,uGAAuG;AACvG,MAAM,OAAO,YAAa,SAAQ,QAAQ;IACxC,YAAmB,OAAe;QAChC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,mFAAmF;AACnF,MAAM,OAAO,SAAU,SAAQ,QAAQ;IACrB,MAAM,CAAS;IACf,IAAI,CAAU;IAE9B,YAAmB,OAAe,EAAE,MAAc,EAAE,IAAc;QAChE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClB,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import type { GitLabConfig } from './config.js';
|
|
2
|
+
/** Everything a caller needs after following pages: the items plus why paging stopped. */
|
|
3
|
+
export interface ListResult<T = unknown> {
|
|
4
|
+
readonly items: readonly T[];
|
|
5
|
+
/** The instance's reported match count, when it reports one. */
|
|
6
|
+
readonly total?: number;
|
|
7
|
+
readonly pages: number;
|
|
8
|
+
/** False when the caller's limit cut the results short. */
|
|
9
|
+
readonly complete: boolean;
|
|
10
|
+
}
|
|
11
|
+
export type ProjectRef = string | number;
|
|
12
|
+
export interface ListProjectsOptions {
|
|
13
|
+
readonly membership?: boolean;
|
|
14
|
+
readonly owned?: boolean;
|
|
15
|
+
readonly simple?: boolean;
|
|
16
|
+
readonly search?: string;
|
|
17
|
+
readonly orderBy?: string;
|
|
18
|
+
readonly sort?: 'asc' | 'desc';
|
|
19
|
+
}
|
|
20
|
+
export interface ListBranchesOptions {
|
|
21
|
+
readonly search?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ListTagsOptions {
|
|
24
|
+
readonly search?: string;
|
|
25
|
+
readonly orderBy?: string;
|
|
26
|
+
readonly sort?: 'asc' | 'desc';
|
|
27
|
+
}
|
|
28
|
+
export interface ListCommitsOptions {
|
|
29
|
+
readonly ref?: string;
|
|
30
|
+
readonly since?: string;
|
|
31
|
+
readonly until?: string;
|
|
32
|
+
readonly path?: string;
|
|
33
|
+
readonly author?: string;
|
|
34
|
+
readonly withStats?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export type MergeRequestState = 'opened' | 'closed' | 'locked' | 'merged' | 'all';
|
|
37
|
+
export interface ListMergeRequestsOptions {
|
|
38
|
+
readonly state?: MergeRequestState;
|
|
39
|
+
readonly sourceBranch?: string;
|
|
40
|
+
readonly targetBranch?: string;
|
|
41
|
+
readonly search?: string;
|
|
42
|
+
readonly authorUsername?: string;
|
|
43
|
+
readonly reviewerUsername?: string;
|
|
44
|
+
readonly labels?: readonly string[];
|
|
45
|
+
readonly orderBy?: string;
|
|
46
|
+
readonly sort?: 'asc' | 'desc';
|
|
47
|
+
}
|
|
48
|
+
export type JobScope = 'created' | 'pending' | 'running' | 'failed' | 'success' | 'canceled' | 'skipped' | 'waiting_for_resource' | 'manual';
|
|
49
|
+
export interface ListJobsOptions {
|
|
50
|
+
readonly scope?: readonly JobScope[];
|
|
51
|
+
}
|
|
52
|
+
export interface ListEnvironmentsOptions {
|
|
53
|
+
readonly name?: string;
|
|
54
|
+
readonly search?: string;
|
|
55
|
+
readonly states?: 'available' | 'stopped';
|
|
56
|
+
}
|
|
57
|
+
export type SearchScope = 'projects' | 'issues' | 'merge_requests' | 'milestones' | 'wiki_blobs' | 'commits' | 'blobs' | 'notes' | 'users' | 'snippet_titles';
|
|
58
|
+
export interface SearchOptions {
|
|
59
|
+
readonly scope: SearchScope;
|
|
60
|
+
readonly search: string;
|
|
61
|
+
/** Narrow to one project. Mutually exclusive with `group`. */
|
|
62
|
+
readonly project?: ProjectRef;
|
|
63
|
+
/** Narrow to one group. Mutually exclusive with `project`. */
|
|
64
|
+
readonly group?: ProjectRef;
|
|
65
|
+
/** Only meaningful for the `blobs` scope: a `filename:`/`path:`-style filter GitLab understands. */
|
|
66
|
+
readonly ref?: string;
|
|
67
|
+
}
|
|
68
|
+
export interface FileOptions {
|
|
69
|
+
readonly ref: string;
|
|
70
|
+
}
|
|
71
|
+
export interface WriteFileInput {
|
|
72
|
+
readonly branch: string;
|
|
73
|
+
readonly content: string;
|
|
74
|
+
readonly commitMessage: string;
|
|
75
|
+
readonly encoding?: 'text' | 'base64';
|
|
76
|
+
readonly authorEmail?: string;
|
|
77
|
+
readonly authorName?: string;
|
|
78
|
+
readonly startBranch?: string;
|
|
79
|
+
/** Update only: the last known commit id for the file, so a concurrent edit is rejected. */
|
|
80
|
+
readonly lastCommitId?: string;
|
|
81
|
+
}
|
|
82
|
+
export interface CommitAction {
|
|
83
|
+
readonly action: 'create' | 'delete' | 'move' | 'update' | 'chmod';
|
|
84
|
+
readonly file_path: string;
|
|
85
|
+
readonly previous_path?: string;
|
|
86
|
+
readonly content?: string;
|
|
87
|
+
readonly encoding?: 'text' | 'base64';
|
|
88
|
+
readonly last_commit_id?: string;
|
|
89
|
+
readonly execute_filemode?: boolean;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* GitLab REST client. Owns the two things callers should never have to think about: addressing a
|
|
93
|
+
* project by path rather than id, and following offset pagination to a caller's limit while
|
|
94
|
+
* reporting honestly whether anything was left behind.
|
|
95
|
+
*/
|
|
96
|
+
export declare class GitLabClient {
|
|
97
|
+
/** The instance this client talks to, for callers that need to render a URL beside a result. */
|
|
98
|
+
readonly url: string;
|
|
99
|
+
private readonly transport;
|
|
100
|
+
constructor(config: GitLabConfig);
|
|
101
|
+
listProjects(options?: ListProjectsOptions, limit?: number): Promise<ListResult>;
|
|
102
|
+
getProject(project: ProjectRef): Promise<unknown>;
|
|
103
|
+
getFile(project: ProjectRef, filePath: string, options: FileOptions): Promise<unknown>;
|
|
104
|
+
createFile(project: ProjectRef, filePath: string, input: WriteFileInput): Promise<unknown>;
|
|
105
|
+
updateFile(project: ProjectRef, filePath: string, input: WriteFileInput): Promise<unknown>;
|
|
106
|
+
listBranches(project: ProjectRef, options?: ListBranchesOptions, limit?: number): Promise<ListResult>;
|
|
107
|
+
createBranch(project: ProjectRef, branch: string, ref: string): Promise<unknown>;
|
|
108
|
+
listCommits(project: ProjectRef, options?: ListCommitsOptions, limit?: number): Promise<ListResult>;
|
|
109
|
+
getCommit(project: ProjectRef, sha: string): Promise<unknown>;
|
|
110
|
+
getCommitDiff(project: ProjectRef, sha: string): Promise<unknown>;
|
|
111
|
+
createCommit(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
112
|
+
listMergeRequests(project: ProjectRef, options?: ListMergeRequestsOptions, limit?: number): Promise<ListResult>;
|
|
113
|
+
getMergeRequest(project: ProjectRef, iid: number): Promise<unknown>;
|
|
114
|
+
createMergeRequest(project: ProjectRef, body: Record<string, unknown>): Promise<unknown>;
|
|
115
|
+
updateMergeRequest(project: ProjectRef, iid: number, body: Record<string, unknown>): Promise<unknown>;
|
|
116
|
+
listTags(project: ProjectRef, options?: ListTagsOptions, limit?: number): Promise<ListResult>;
|
|
117
|
+
listJobs(project: ProjectRef, options?: ListJobsOptions, limit?: number): Promise<ListResult>;
|
|
118
|
+
/** A job's trace is plain text, sometimes megabytes of it, and is never JSON. */
|
|
119
|
+
getJobLog(project: ProjectRef, jobId: number): Promise<string>;
|
|
120
|
+
listVariables(project: ProjectRef, limit?: number): Promise<ListResult>;
|
|
121
|
+
listEnvironments(project: ProjectRef, options?: ListEnvironmentsOptions, limit?: number): Promise<ListResult>;
|
|
122
|
+
listReleases(project: ProjectRef, limit?: number): Promise<ListResult>;
|
|
123
|
+
/**
|
|
124
|
+
* Global, group, or project search. GitLab exposes these as three separate endpoints rather
|
|
125
|
+
* than one endpoint with a filter, and the scopes each accepts differ, so the caller's choice
|
|
126
|
+
* of `project` or `group` selects the route instead of becoming a query parameter.
|
|
127
|
+
*/
|
|
128
|
+
search(options: SearchOptions, limit?: number): Promise<ListResult>;
|
|
129
|
+
private request;
|
|
130
|
+
/**
|
|
131
|
+
* Follows offset pagination until the limit is reached or the instance says there is no next
|
|
132
|
+
* page, and reports which of the two happened. A caller that gets `complete: false` was cut
|
|
133
|
+
* short by its own limit and knows more exists — the alternative, returning a short list with
|
|
134
|
+
* no signal, is how a partial answer gets mistaken for the whole one.
|
|
135
|
+
*/
|
|
136
|
+
private collect;
|
|
137
|
+
}
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2026, SimplySF.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { buildAuthHeaders } from './auth.js';
|
|
17
|
+
import { ConfigError } from './errors.js';
|
|
18
|
+
import { HttpTransport } from './http.js';
|
|
19
|
+
/** GitLab's own ceiling on `per_page`; asking for more is silently clamped to it. */
|
|
20
|
+
const MAX_PER_PAGE = 100;
|
|
21
|
+
/** How many pages one list call will follow before giving up, however small the page size. */
|
|
22
|
+
const MAX_PAGES = 100;
|
|
23
|
+
const API_BASE = '/api/v4';
|
|
24
|
+
/**
|
|
25
|
+
* A path segment built from user input. GitLab addresses a project either by numeric id or by its
|
|
26
|
+
* URL-encoded full path, so `group/project` has to arrive as `group%2Fproject` — an un-encoded
|
|
27
|
+
* slash reads as a different route entirely and answers 404 with nothing pointing at the cause.
|
|
28
|
+
*/
|
|
29
|
+
function segment(value) {
|
|
30
|
+
const text = String(value).trim();
|
|
31
|
+
if (text === '')
|
|
32
|
+
throw new ConfigError('A project id or path is required, but an empty value was given.');
|
|
33
|
+
return encodeURIComponent(text);
|
|
34
|
+
}
|
|
35
|
+
/** Drops the entries a caller left unset so they never reach the query string as "undefined". */
|
|
36
|
+
function query(pairs) {
|
|
37
|
+
return Object.fromEntries(Object.entries(pairs).filter(([, value]) => value !== undefined));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* GitLab REST client. Owns the two things callers should never have to think about: addressing a
|
|
41
|
+
* project by path rather than id, and following offset pagination to a caller's limit while
|
|
42
|
+
* reporting honestly whether anything was left behind.
|
|
43
|
+
*/
|
|
44
|
+
export class GitLabClient {
|
|
45
|
+
/** The instance this client talks to, for callers that need to render a URL beside a result. */
|
|
46
|
+
url;
|
|
47
|
+
transport;
|
|
48
|
+
constructor(config) {
|
|
49
|
+
this.url = config.url;
|
|
50
|
+
this.transport = new HttpTransport({
|
|
51
|
+
baseUrl: config.url,
|
|
52
|
+
headers: buildAuthHeaders(config),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// --- Projects ---
|
|
56
|
+
listProjects(options = {}, limit = 20) {
|
|
57
|
+
return this.collect('/projects', query({
|
|
58
|
+
membership: options.membership,
|
|
59
|
+
owned: options.owned,
|
|
60
|
+
simple: options.simple,
|
|
61
|
+
search: options.search,
|
|
62
|
+
order_by: options.orderBy,
|
|
63
|
+
sort: options.sort,
|
|
64
|
+
}), limit);
|
|
65
|
+
}
|
|
66
|
+
async getProject(project) {
|
|
67
|
+
return this.request(`/projects/${segment(project)}`, { method: 'GET' });
|
|
68
|
+
}
|
|
69
|
+
// --- Repository files ---
|
|
70
|
+
async getFile(project, filePath, options) {
|
|
71
|
+
return this.request(`/projects/${segment(project)}/repository/files/${segment(filePath)}`, {
|
|
72
|
+
method: 'GET',
|
|
73
|
+
query: { ref: options.ref },
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async createFile(project, filePath, input) {
|
|
77
|
+
return this.request(`/projects/${segment(project)}/repository/files/${segment(filePath)}`, {
|
|
78
|
+
method: 'POST',
|
|
79
|
+
body: fileBody(input),
|
|
80
|
+
mutating: true,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async updateFile(project, filePath, input) {
|
|
84
|
+
return this.request(`/projects/${segment(project)}/repository/files/${segment(filePath)}`, {
|
|
85
|
+
method: 'PUT',
|
|
86
|
+
body: fileBody(input),
|
|
87
|
+
mutating: true,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
// --- Branches ---
|
|
91
|
+
async listBranches(project, options = {}, limit = 20) {
|
|
92
|
+
return this.collect(`/projects/${segment(project)}/repository/branches`, query({ search: options.search }), limit);
|
|
93
|
+
}
|
|
94
|
+
async createBranch(project, branch, ref) {
|
|
95
|
+
return this.request(`/projects/${segment(project)}/repository/branches`, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
body: { branch, ref },
|
|
98
|
+
mutating: true,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// --- Commits ---
|
|
102
|
+
async listCommits(project, options = {}, limit = 20) {
|
|
103
|
+
return this.collect(`/projects/${segment(project)}/repository/commits`, query({
|
|
104
|
+
ref_name: options.ref,
|
|
105
|
+
since: options.since,
|
|
106
|
+
until: options.until,
|
|
107
|
+
path: options.path,
|
|
108
|
+
author: options.author,
|
|
109
|
+
with_stats: options.withStats,
|
|
110
|
+
}), limit);
|
|
111
|
+
}
|
|
112
|
+
async getCommit(project, sha) {
|
|
113
|
+
return this.request(`/projects/${segment(project)}/repository/commits/${segment(sha)}`, { method: 'GET' });
|
|
114
|
+
}
|
|
115
|
+
async getCommitDiff(project, sha) {
|
|
116
|
+
return this.request(`/projects/${segment(project)}/repository/commits/${segment(sha)}/diff`, { method: 'GET' });
|
|
117
|
+
}
|
|
118
|
+
async createCommit(project, body) {
|
|
119
|
+
return this.request(`/projects/${segment(project)}/repository/commits`, { method: 'POST', body, mutating: true });
|
|
120
|
+
}
|
|
121
|
+
// --- Merge requests ---
|
|
122
|
+
async listMergeRequests(project, options = {}, limit = 20) {
|
|
123
|
+
return this.collect(`/projects/${segment(project)}/merge_requests`, query({
|
|
124
|
+
state: options.state,
|
|
125
|
+
source_branch: options.sourceBranch,
|
|
126
|
+
target_branch: options.targetBranch,
|
|
127
|
+
search: options.search,
|
|
128
|
+
author_username: options.authorUsername,
|
|
129
|
+
reviewer_username: options.reviewerUsername,
|
|
130
|
+
labels: options.labels === undefined ? undefined : options.labels.join(','),
|
|
131
|
+
order_by: options.orderBy,
|
|
132
|
+
sort: options.sort,
|
|
133
|
+
}), limit);
|
|
134
|
+
}
|
|
135
|
+
async getMergeRequest(project, iid) {
|
|
136
|
+
return this.request(`/projects/${segment(project)}/merge_requests/${iid}`, { method: 'GET' });
|
|
137
|
+
}
|
|
138
|
+
async createMergeRequest(project, body) {
|
|
139
|
+
return this.request(`/projects/${segment(project)}/merge_requests`, { method: 'POST', body, mutating: true });
|
|
140
|
+
}
|
|
141
|
+
async updateMergeRequest(project, iid, body) {
|
|
142
|
+
return this.request(`/projects/${segment(project)}/merge_requests/${iid}`, {
|
|
143
|
+
method: 'PUT',
|
|
144
|
+
body,
|
|
145
|
+
mutating: true,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
// --- Tags ---
|
|
149
|
+
async listTags(project, options = {}, limit = 20) {
|
|
150
|
+
return this.collect(`/projects/${segment(project)}/repository/tags`, query({ search: options.search, order_by: options.orderBy, sort: options.sort }), limit);
|
|
151
|
+
}
|
|
152
|
+
// --- CI/CD ---
|
|
153
|
+
async listJobs(project, options = {}, limit = 20) {
|
|
154
|
+
return this.collect(`/projects/${segment(project)}/jobs`, query({ scope: options.scope }), limit);
|
|
155
|
+
}
|
|
156
|
+
/** A job's trace is plain text, sometimes megabytes of it, and is never JSON. */
|
|
157
|
+
async getJobLog(project, jobId) {
|
|
158
|
+
return this.transport.text({
|
|
159
|
+
method: 'GET',
|
|
160
|
+
path: `${API_BASE}/projects/${segment(project)}/jobs/${jobId}/trace`,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
async listVariables(project, limit = 20) {
|
|
164
|
+
return this.collect(`/projects/${segment(project)}/variables`, {}, limit);
|
|
165
|
+
}
|
|
166
|
+
async listEnvironments(project, options = {}, limit = 20) {
|
|
167
|
+
return this.collect(`/projects/${segment(project)}/environments`, query({ name: options.name, search: options.search, states: options.states }), limit);
|
|
168
|
+
}
|
|
169
|
+
// --- Releases ---
|
|
170
|
+
async listReleases(project, limit = 20) {
|
|
171
|
+
return this.collect(`/projects/${segment(project)}/releases`, {}, limit);
|
|
172
|
+
}
|
|
173
|
+
// --- Search ---
|
|
174
|
+
/**
|
|
175
|
+
* Global, group, or project search. GitLab exposes these as three separate endpoints rather
|
|
176
|
+
* than one endpoint with a filter, and the scopes each accepts differ, so the caller's choice
|
|
177
|
+
* of `project` or `group` selects the route instead of becoming a query parameter.
|
|
178
|
+
*/
|
|
179
|
+
async search(options, limit = 20) {
|
|
180
|
+
if (options.project !== undefined && options.group !== undefined) {
|
|
181
|
+
throw new ConfigError('Search within a project or within a group, not both.');
|
|
182
|
+
}
|
|
183
|
+
const path = options.project !== undefined
|
|
184
|
+
? `/projects/${segment(options.project)}/search`
|
|
185
|
+
: options.group !== undefined
|
|
186
|
+
? `/groups/${segment(options.group)}/search`
|
|
187
|
+
: '/search';
|
|
188
|
+
return this.collect(path, query({ scope: options.scope, search: options.search, ref: options.ref }), limit);
|
|
189
|
+
}
|
|
190
|
+
// --- Plumbing ---
|
|
191
|
+
request(path, call) {
|
|
192
|
+
return this.transport.json({ ...call, path: `${API_BASE}${path}` });
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Follows offset pagination until the limit is reached or the instance says there is no next
|
|
196
|
+
* page, and reports which of the two happened. A caller that gets `complete: false` was cut
|
|
197
|
+
* short by its own limit and knows more exists — the alternative, returning a short list with
|
|
198
|
+
* no signal, is how a partial answer gets mistaken for the whole one.
|
|
199
|
+
*/
|
|
200
|
+
async collect(path, params, limit) {
|
|
201
|
+
if (!Number.isInteger(limit) || limit < 1) {
|
|
202
|
+
throw new ConfigError(`A limit must be a positive whole number, got ${limit}.`);
|
|
203
|
+
}
|
|
204
|
+
const items = [];
|
|
205
|
+
let page = 1;
|
|
206
|
+
let pages = 0;
|
|
207
|
+
let total;
|
|
208
|
+
/* Paging is sequential by definition: each request needs the previous page's cursor. */
|
|
209
|
+
/* eslint-disable no-await-in-loop */
|
|
210
|
+
while (items.length < limit && pages < MAX_PAGES) {
|
|
211
|
+
const response = await this.transport.jsonPaged({
|
|
212
|
+
method: 'GET',
|
|
213
|
+
path: `${API_BASE}${path}`,
|
|
214
|
+
query: { ...params, per_page: Math.min(MAX_PER_PAGE, limit - items.length), page },
|
|
215
|
+
});
|
|
216
|
+
pages += 1;
|
|
217
|
+
total = response.pagination.total ?? total;
|
|
218
|
+
// Every list endpoint answers with a JSON array. A non-array here means the route matched
|
|
219
|
+
// something else — a redirect to a sign-in page, most often — and slicing it would produce
|
|
220
|
+
// a confidently empty list rather than an error.
|
|
221
|
+
if (!Array.isArray(response.data)) {
|
|
222
|
+
throw new ConfigError(`GET ${path} did not return a list. Check that the URL points at a GitLab instance.`);
|
|
223
|
+
}
|
|
224
|
+
items.push(...response.data);
|
|
225
|
+
const nextPage = response.pagination.nextPage;
|
|
226
|
+
// No next-page header, an empty page, or a cursor that fails to advance all mean the same
|
|
227
|
+
// thing: this was the last page. The last of the three matters because a misconfigured
|
|
228
|
+
// reverse proxy that strips or pins the header would otherwise loop until MAX_PAGES.
|
|
229
|
+
if (response.data.length === 0 || nextPage === undefined || nextPage <= page) {
|
|
230
|
+
return { items: items.slice(0, limit), total, pages, complete: true };
|
|
231
|
+
}
|
|
232
|
+
page = nextPage;
|
|
233
|
+
}
|
|
234
|
+
/* eslint-enable no-await-in-loop */
|
|
235
|
+
return { items: items.slice(0, limit), total, pages, complete: false };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function fileBody(input) {
|
|
239
|
+
return {
|
|
240
|
+
branch: input.branch,
|
|
241
|
+
content: input.content,
|
|
242
|
+
commit_message: input.commitMessage,
|
|
243
|
+
...(input.encoding === undefined ? {} : { encoding: input.encoding }),
|
|
244
|
+
...(input.authorEmail === undefined ? {} : { author_email: input.authorEmail }),
|
|
245
|
+
...(input.authorName === undefined ? {} : { author_name: input.authorName }),
|
|
246
|
+
...(input.startBranch === undefined ? {} : { start_branch: input.startBranch }),
|
|
247
|
+
...(input.lastCommitId === undefined ? {} : { last_commit_id: input.lastCommitId }),
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
//# sourceMappingURL=gitlab-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gitlab-client.js","sourceRoot":"","sources":["../src/gitlab-client.ts"],"names":[],"mappings":"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"}
|
package/lib/http.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/** A single query value, or a repeated one — GitLab spells repeats `scope[]=a&scope[]=b`. */
|
|
2
|
+
export type QueryValue = string | number | boolean | undefined | ReadonlyArray<string | number>;
|
|
3
|
+
/** One REST call, relative to the transport's base URL. */
|
|
4
|
+
export interface JsonCall {
|
|
5
|
+
readonly method: string;
|
|
6
|
+
readonly path: string;
|
|
7
|
+
readonly query?: Record<string, QueryValue>;
|
|
8
|
+
readonly body?: unknown;
|
|
9
|
+
/**
|
|
10
|
+
* Whether this call changes data. Declared by the caller rather than guessed from the verb,
|
|
11
|
+
* because the verb is not a reliable signal: GitLab's global search is a GET that a `read_api`
|
|
12
|
+
* token can run, while its markdown renderer is a POST that changes nothing. Guessing wrong
|
|
13
|
+
* here is worse than not guessing — see the 403 handling below.
|
|
14
|
+
*/
|
|
15
|
+
readonly mutating?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Where and how a transport talks: fixed per client instance. */
|
|
18
|
+
export interface TransportTarget {
|
|
19
|
+
readonly baseUrl: string;
|
|
20
|
+
readonly headers: Record<string, string>;
|
|
21
|
+
readonly timeoutMs?: number;
|
|
22
|
+
readonly maxAttempts?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* GitLab's offset-pagination headers, as far as they were sent. Keyset-paginated endpoints and
|
|
26
|
+
* any endpoint whose result set is too large for GitLab to count omit the totals, so every field
|
|
27
|
+
* here is optional and a caller must treat a missing one as "unknown", never as zero.
|
|
28
|
+
*/
|
|
29
|
+
export interface Pagination {
|
|
30
|
+
readonly nextPage?: number;
|
|
31
|
+
readonly total?: number;
|
|
32
|
+
readonly totalPages?: number;
|
|
33
|
+
readonly perPage?: number;
|
|
34
|
+
}
|
|
35
|
+
/** A response body together with the pagination GitLab reported alongside it. */
|
|
36
|
+
export interface Paged<T> {
|
|
37
|
+
readonly data: T;
|
|
38
|
+
readonly pagination: Pagination;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* JSON-over-HTTPS transport bound to one GitLab instance. Each client owns one; everything that
|
|
42
|
+
* would otherwise be an argument (base URL, auth headers, timing) is fixed at construction so
|
|
43
|
+
* call sites stay small.
|
|
44
|
+
*
|
|
45
|
+
* Certificate verification is always on. An instance behind an internal or agency CA is
|
|
46
|
+
* supported by pointing Node at that CA — `NODE_EXTRA_CA_CERTS=/path/to/ca.pem` — which keeps
|
|
47
|
+
* verification intact rather than turning it off.
|
|
48
|
+
*/
|
|
49
|
+
export declare class HttpTransport {
|
|
50
|
+
private readonly target;
|
|
51
|
+
private readonly timeoutMs;
|
|
52
|
+
private readonly maxAttempts;
|
|
53
|
+
constructor(target: TransportTarget);
|
|
54
|
+
/**
|
|
55
|
+
* Executes a call and parses the JSON response. 401/403 become `AuthError` immediately;
|
|
56
|
+
* 429/5xx retry with exponential backoff, deferring to a (capped) `Retry-After` when one is
|
|
57
|
+
* sent; other non-2xx become `HttpError`. Timeouts, DNS misses, and certificate failures
|
|
58
|
+
* become `NetworkError` without pointless retries; transient socket failures retry. The
|
|
59
|
+
* timeout covers the whole exchange, response body included.
|
|
60
|
+
*/
|
|
61
|
+
json<T>(call: JsonCall): Promise<T>;
|
|
62
|
+
/**
|
|
63
|
+
* The same call as {@link json}, keeping the pagination headers GitLab answered with. Separate
|
|
64
|
+
* from `json` because pagination is only meaningful for list endpoints, and a caller that
|
|
65
|
+
* ignored the headers on one would silently return a first page as if it were everything.
|
|
66
|
+
*/
|
|
67
|
+
jsonPaged<T>(call: JsonCall): Promise<Paged<T>>;
|
|
68
|
+
/**
|
|
69
|
+
* Executes a call whose response is not JSON and returns it verbatim.
|
|
70
|
+
*
|
|
71
|
+
* Job traces are plain text and repository file contents can be raw bytes; parsing either as
|
|
72
|
+
* JSON would fail on the successful case. Errors on these endpoints still come back as JSON,
|
|
73
|
+
* and are still raised as `HttpError`, because the failure path is shared.
|
|
74
|
+
*/
|
|
75
|
+
text(call: JsonCall): Promise<string>;
|
|
76
|
+
private send;
|
|
77
|
+
/** Runs one attempt under a deadline that spans connection, headers, AND body. */
|
|
78
|
+
private attempt;
|
|
79
|
+
/** Wraps raw transport failures in typed, user-explainable errors; rethrows deliberate ones. */
|
|
80
|
+
private triage;
|
|
81
|
+
private resolve;
|
|
82
|
+
}
|