@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/http.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
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 { AuthError, CliError, HttpError, NetworkError } from './errors.js';
|
|
17
|
+
import { stripControlOneLine } from './text.js';
|
|
18
|
+
const DEFAULT_TIMEOUT_MS = 30_000;
|
|
19
|
+
const DEFAULT_MAX_ATTEMPTS = 3;
|
|
20
|
+
const BACKOFF_BASE_MS = 500;
|
|
21
|
+
const RETRY_AFTER_CAP_MS = 60_000;
|
|
22
|
+
/** Certificate problems: the trust store is wrong, so retrying changes nothing. */
|
|
23
|
+
const CERT_CODES = new Set([
|
|
24
|
+
'CERT_HAS_EXPIRED',
|
|
25
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
26
|
+
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
27
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
28
|
+
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
29
|
+
]);
|
|
30
|
+
/** Transport failures another attempt cannot fix: the host or the trust decision is wrong. */
|
|
31
|
+
const PERMANENT_TRANSPORT_CODES = new Set(['ENOTFOUND', 'EAI_AGAIN', ...CERT_CODES]);
|
|
32
|
+
function delay(ms) {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
setTimeout(resolve, ms);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/** Digs the syscall-level error code out of however deeply the runtime wrapped it. */
|
|
38
|
+
function transportCode(error) {
|
|
39
|
+
for (let cursor = error; cursor instanceof Error; cursor = cursor.cause) {
|
|
40
|
+
const code = cursor.code;
|
|
41
|
+
if (typeof code === 'string')
|
|
42
|
+
return code;
|
|
43
|
+
}
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
function isAbort(error) {
|
|
47
|
+
// Deliberately not `instanceof Error`: fetch aborts reject with a DOMException, which isn't one.
|
|
48
|
+
return typeof error === 'object' && error !== null && error.name === 'AbortError';
|
|
49
|
+
}
|
|
50
|
+
/** Reads one `X-…` pagination header as a positive integer, or nothing if it is absent or junk. */
|
|
51
|
+
function headerNumber(headers, name) {
|
|
52
|
+
const raw = headers.get(name);
|
|
53
|
+
if (raw === null || raw.trim() === '')
|
|
54
|
+
return undefined;
|
|
55
|
+
const value = Number(raw);
|
|
56
|
+
return Number.isInteger(value) && value >= 0 ? value : undefined;
|
|
57
|
+
}
|
|
58
|
+
function readPagination(headers) {
|
|
59
|
+
return {
|
|
60
|
+
nextPage: headerNumber(headers, 'x-next-page'),
|
|
61
|
+
total: headerNumber(headers, 'x-total'),
|
|
62
|
+
totalPages: headerNumber(headers, 'x-total-pages'),
|
|
63
|
+
perPage: headerNumber(headers, 'x-per-page'),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* JSON-over-HTTPS transport bound to one GitLab instance. Each client owns one; everything that
|
|
68
|
+
* would otherwise be an argument (base URL, auth headers, timing) is fixed at construction so
|
|
69
|
+
* call sites stay small.
|
|
70
|
+
*
|
|
71
|
+
* Certificate verification is always on. An instance behind an internal or agency CA is
|
|
72
|
+
* supported by pointing Node at that CA — `NODE_EXTRA_CA_CERTS=/path/to/ca.pem` — which keeps
|
|
73
|
+
* verification intact rather than turning it off.
|
|
74
|
+
*/
|
|
75
|
+
export class HttpTransport {
|
|
76
|
+
target;
|
|
77
|
+
timeoutMs;
|
|
78
|
+
maxAttempts;
|
|
79
|
+
constructor(target) {
|
|
80
|
+
this.target = target;
|
|
81
|
+
this.timeoutMs = target.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
82
|
+
this.maxAttempts = target.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Executes a call and parses the JSON response. 401/403 become `AuthError` immediately;
|
|
86
|
+
* 429/5xx retry with exponential backoff, deferring to a (capped) `Retry-After` when one is
|
|
87
|
+
* sent; other non-2xx become `HttpError`. Timeouts, DNS misses, and certificate failures
|
|
88
|
+
* become `NetworkError` without pointless retries; transient socket failures retry. The
|
|
89
|
+
* timeout covers the whole exchange, response body included.
|
|
90
|
+
*/
|
|
91
|
+
async json(call) {
|
|
92
|
+
return (await this.send(call, 'json')).data;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The same call as {@link json}, keeping the pagination headers GitLab answered with. Separate
|
|
96
|
+
* from `json` because pagination is only meaningful for list endpoints, and a caller that
|
|
97
|
+
* ignored the headers on one would silently return a first page as if it were everything.
|
|
98
|
+
*/
|
|
99
|
+
jsonPaged(call) {
|
|
100
|
+
return this.send(call, 'json');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Executes a call whose response is not JSON and returns it verbatim.
|
|
104
|
+
*
|
|
105
|
+
* Job traces are plain text and repository file contents can be raw bytes; parsing either as
|
|
106
|
+
* JSON would fail on the successful case. Errors on these endpoints still come back as JSON,
|
|
107
|
+
* and are still raised as `HttpError`, because the failure path is shared.
|
|
108
|
+
*/
|
|
109
|
+
async text(call) {
|
|
110
|
+
return (await this.send(call, 'text')).data;
|
|
111
|
+
}
|
|
112
|
+
async send(call, mode) {
|
|
113
|
+
const url = this.resolve(call);
|
|
114
|
+
/* Each attempt must observe the previous one's outcome before starting — sequential awaiting
|
|
115
|
+
is the mechanism, not an accident. */
|
|
116
|
+
/* eslint-disable no-await-in-loop */
|
|
117
|
+
for (let attempt = 1;; attempt += 1) {
|
|
118
|
+
let outcome;
|
|
119
|
+
try {
|
|
120
|
+
outcome = await this.attempt(url, call, mode);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const verdict = this.triage(error, call, url, attempt);
|
|
124
|
+
if (verdict !== TRANSIENT)
|
|
125
|
+
throw verdict;
|
|
126
|
+
await delay(BACKOFF_BASE_MS * 2 ** (attempt - 1));
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (outcome.done)
|
|
130
|
+
return outcome.value;
|
|
131
|
+
if (attempt >= this.maxAttempts)
|
|
132
|
+
throw outcome.exhausted();
|
|
133
|
+
await delay(outcome.delayMs ?? BACKOFF_BASE_MS * 2 ** (attempt - 1));
|
|
134
|
+
}
|
|
135
|
+
/* eslint-enable no-await-in-loop */
|
|
136
|
+
}
|
|
137
|
+
/** Runs one attempt under a deadline that spans connection, headers, AND body. */
|
|
138
|
+
async attempt(url, call, mode) {
|
|
139
|
+
const controller = new AbortController();
|
|
140
|
+
const deadline = setTimeout(() => {
|
|
141
|
+
controller.abort();
|
|
142
|
+
}, this.timeoutMs);
|
|
143
|
+
try {
|
|
144
|
+
const headers = {
|
|
145
|
+
Accept: mode === 'json' ? 'application/json' : '*/*',
|
|
146
|
+
...this.target.headers,
|
|
147
|
+
};
|
|
148
|
+
if (call.body !== undefined)
|
|
149
|
+
headers['Content-Type'] = 'application/json';
|
|
150
|
+
const response = await fetch(url, {
|
|
151
|
+
method: call.method,
|
|
152
|
+
headers,
|
|
153
|
+
body: call.body === undefined ? undefined : JSON.stringify(call.body),
|
|
154
|
+
signal: controller.signal,
|
|
155
|
+
});
|
|
156
|
+
if (response.status === 401 || response.status === 403) {
|
|
157
|
+
const detail = await describeBody(response);
|
|
158
|
+
// A 403 on a call that really does change data is often a credential that can read but
|
|
159
|
+
// not write, so naming that possibility saves a confusing hunt. It is phrased as an
|
|
160
|
+
// observation rather than an instruction, and gated on the caller's declared intent:
|
|
161
|
+
// an agent reading "use a credential with write scope" after an ordinary search
|
|
162
|
+
// failure would escalate its own privileges, defeating the one boundary that binds.
|
|
163
|
+
const hint = response.status === 403 && call.mutating === true
|
|
164
|
+
? ' This token may be read_api-scoped, or your role on the project may not permit this.'
|
|
165
|
+
: '';
|
|
166
|
+
throw new AuthError(`Authentication failed: ${call.method} ${call.path} returned ${response.status}.${detail}${hint}`, response.status);
|
|
167
|
+
}
|
|
168
|
+
if (response.ok) {
|
|
169
|
+
const text = await response.text();
|
|
170
|
+
const pagination = readPagination(response.headers);
|
|
171
|
+
if (mode === 'text')
|
|
172
|
+
return { done: true, value: { data: text, pagination } };
|
|
173
|
+
if (text.trim() === '')
|
|
174
|
+
return { done: true, value: { data: {}, pagination } };
|
|
175
|
+
try {
|
|
176
|
+
return { done: true, value: { data: JSON.parse(text), pagination } };
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
throw new HttpError(`${call.method} ${call.path} returned malformed JSON.`, response.status, text);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (response.status === 429 || response.status >= 500) {
|
|
183
|
+
const retryAfter = readRetryAfter(response.headers.get('retry-after'));
|
|
184
|
+
const status = response.status;
|
|
185
|
+
const detail = await describeBody(response);
|
|
186
|
+
return {
|
|
187
|
+
done: false,
|
|
188
|
+
delayMs: retryAfter,
|
|
189
|
+
exhausted: () => new HttpError(`${call.method} ${call.path} failed (${status}).${detail}`, status),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
const body = await bodyAsJsonOrText(response);
|
|
193
|
+
throw new HttpError(`${call.method} ${call.path} failed (${response.status}).${formatSnippet(body)}`, response.status, body);
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
clearTimeout(deadline);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/** Wraps raw transport failures in typed, user-explainable errors; rethrows deliberate ones. */
|
|
200
|
+
triage(error, call, url, attempt) {
|
|
201
|
+
if (error instanceof CliError)
|
|
202
|
+
return error;
|
|
203
|
+
if (isAbort(error)) {
|
|
204
|
+
return new NetworkError(`${call.method} ${call.path} did not complete within ${this.timeoutMs} ms (${url.host}).`);
|
|
205
|
+
}
|
|
206
|
+
const code = transportCode(error);
|
|
207
|
+
if (code !== undefined && PERMANENT_TRANSPORT_CODES.has(code)) {
|
|
208
|
+
const hint = CERT_CODES.has(code)
|
|
209
|
+
? ` The certificate could not be verified. If ${url.host} is behind an internal or agency CA, point Node at that CA bundle with NODE_EXTRA_CA_CERTS=/path/to/ca.pem.`
|
|
210
|
+
: '';
|
|
211
|
+
return new NetworkError(`Cannot reach ${url.host}: ${code}.${hint}`);
|
|
212
|
+
}
|
|
213
|
+
if (attempt < this.maxAttempts) {
|
|
214
|
+
// Transient transport failure (connection reset, refused, mid-flight drop): try again.
|
|
215
|
+
return TRANSIENT;
|
|
216
|
+
}
|
|
217
|
+
const description = code ?? (error instanceof Error ? error.message : String(error));
|
|
218
|
+
return new NetworkError(`${call.method} ${call.path} failed after ${attempt} attempts: ${description}.`);
|
|
219
|
+
}
|
|
220
|
+
resolve(call) {
|
|
221
|
+
const url = new URL(this.target.baseUrl);
|
|
222
|
+
const root = url.pathname.replace(/\/+$/, '');
|
|
223
|
+
url.pathname = call.path.startsWith('/') ? `${root}${call.path}` : `${root}/${call.path}`;
|
|
224
|
+
for (const [name, value] of Object.entries(call.query ?? {})) {
|
|
225
|
+
if (value === undefined)
|
|
226
|
+
continue;
|
|
227
|
+
// GitLab reads a repeated parameter only in its bracketed form, so an array is appended as
|
|
228
|
+
// `scope[]=` per element rather than joined — a comma-joined value is taken as one literal
|
|
229
|
+
// scope and silently matches nothing.
|
|
230
|
+
if (Array.isArray(value)) {
|
|
231
|
+
for (const item of value)
|
|
232
|
+
url.searchParams.append(`${name}[]`, String(item));
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
url.searchParams.set(name, String(value));
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return url;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
/** Sentinel: the triage step decided this attempt's failure is worth another try. */
|
|
242
|
+
const TRANSIENT = Symbol('transient-transport-failure');
|
|
243
|
+
/** `Retry-After` arrives as delay-seconds or an HTTP date; both are capped so a hostile value can't park the CLI. */
|
|
244
|
+
function readRetryAfter(header) {
|
|
245
|
+
if (header === null)
|
|
246
|
+
return undefined;
|
|
247
|
+
const value = header.trim();
|
|
248
|
+
if (/^\d+$/.test(value))
|
|
249
|
+
return Math.min(Number(value) * 1000, RETRY_AFTER_CAP_MS);
|
|
250
|
+
const at = Date.parse(value);
|
|
251
|
+
if (Number.isNaN(at))
|
|
252
|
+
return undefined;
|
|
253
|
+
return Math.min(Math.max(at - Date.now(), 0), RETRY_AFTER_CAP_MS);
|
|
254
|
+
}
|
|
255
|
+
/** Drains an error response and renders a short human-readable suffix, never throwing. */
|
|
256
|
+
async function describeBody(response) {
|
|
257
|
+
return formatSnippet(await bodyAsJsonOrText(response));
|
|
258
|
+
}
|
|
259
|
+
async function bodyAsJsonOrText(response) {
|
|
260
|
+
const text = await response.text().catch(() => '');
|
|
261
|
+
if (text === '')
|
|
262
|
+
return undefined;
|
|
263
|
+
try {
|
|
264
|
+
return JSON.parse(text);
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return text;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Renders a response body into an error message, on one line.
|
|
272
|
+
*
|
|
273
|
+
* This is the only point where wholly server-controlled text joins an error message, so it is
|
|
274
|
+
* where the newline has to go — not at the assembled message, which also carries this CLI's own
|
|
275
|
+
* deliberate line structure. A multi-line body would otherwise forge extra stderr lines,
|
|
276
|
+
* including one shaped like this CLI's JSON error object, which a caller parsing stderr
|
|
277
|
+
* line-by-line could not tell from the real thing.
|
|
278
|
+
*/
|
|
279
|
+
function formatSnippet(body) {
|
|
280
|
+
if (body === undefined)
|
|
281
|
+
return '';
|
|
282
|
+
const rendered = stripControlOneLine(typeof body === 'string' ? body : JSON.stringify(body));
|
|
283
|
+
return rendered === '' ? '' : ` ${rendered}`;
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=http.js.map
|
package/lib/http.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export { buildAuthHeaders } from './auth.js';
|
|
2
|
+
export { DEFAULT_URL, resolveGitLabConfig, TOKEN_ENV, URL_ENV, type ConfigOverrides, type EnvLike, type GitLabConfig, } from './config.js';
|
|
3
|
+
export { loadEnvFile, parseEnvFile } from './env-file.js';
|
|
4
|
+
export { AuthError, CliError, ConfigError, HttpError, NetworkError } from './errors.js';
|
|
5
|
+
export { HttpTransport, type JsonCall, type Paged, type Pagination, type QueryValue, type TransportTarget, } from './http.js';
|
|
6
|
+
export { GitLabClient, type CommitAction, type FileOptions, type JobScope, type ListBranchesOptions, type ListCommitsOptions, type ListEnvironmentsOptions, type ListJobsOptions, type ListMergeRequestsOptions, type ListProjectsOptions, type ListResult, type ListTagsOptions, type MergeRequestState, type ProjectRef, type SearchOptions, type SearchScope, type WriteFileInput, } from './gitlab-client.js';
|
|
7
|
+
export { stripControl, stripControlOneLine } from './text.js';
|
|
8
|
+
export { assertWritesAllowed, isReadOnly, READ_ONLY_ENV } from './write-safety.js';
|
|
9
|
+
export { collectSecrets, redactSecrets, sanitiseDeep, SECRET_ENV, secretValues } from './redaction.js';
|
|
10
|
+
export { assertNotEmpty, mergeBody, parseBodyInput, readTextFile } from './json-input.js';
|
|
11
|
+
export { formatKeyValue, formatTable, type Column, type Pair } from './output.js';
|
|
12
|
+
export { branchColumns, commitColumns, environmentColumns, jobColumns, mergeRequestColumns, projectColumns, releaseColumns, tagColumns, variableColumns, type Row, } from './tables.js';
|
|
13
|
+
export { HIDDEN, maskVariables, tailLog } from './cicd.js';
|
|
14
|
+
export { buildCommitBody, parseActionsInput, parseCommitActions, type CommitInput } from './commits.js';
|
|
15
|
+
export { assertIid, buildMergeRequestCreateBody, buildMergeRequestUpdateBody, mergeRequestUrl, type MergeRequestCreateInput, type MergeRequestUpdateInput, } from './merge-requests.js';
|
|
16
|
+
export { buildFileWrite, decodeFileContent, type DecodedFile, type FilePayload, type FileWriteInput, } from './repository-files.js';
|
|
17
|
+
export { ADVANCED_SCOPES, BASIC_SCOPES, needsAdvancedSearch, searchColumns } from './search.js';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
// Everything exported from this file is this package's public API and is semver-covered: adding
|
|
17
|
+
// an export is a minor/patch change, but removing or renaming one is breaking.
|
|
18
|
+
// `test/index.test.ts` pins the exported-key list so an accidental removal fails a test instead
|
|
19
|
+
// of silently shipping in a patch release. See docs/design/0001-gitlab-client-core.md for why
|
|
20
|
+
// this package is split out from `@simplysf/simply-gitlab` (the CLI).
|
|
21
|
+
//
|
|
22
|
+
// Nothing here touches a terminal or a process: no oclif, no child processes, no `process.argv`,
|
|
23
|
+
// nothing written to stdout or stderr. `process.env` is read only through an injectable `env`
|
|
24
|
+
// parameter that defaults to it. The repo's lint config enforces the import side of that rule.
|
|
25
|
+
// --- Configuration, transport, client, errors ---
|
|
26
|
+
export { buildAuthHeaders } from './auth.js';
|
|
27
|
+
export { DEFAULT_URL, resolveGitLabConfig, TOKEN_ENV, URL_ENV, } from './config.js';
|
|
28
|
+
export { loadEnvFile, parseEnvFile } from './env-file.js';
|
|
29
|
+
export { AuthError, CliError, ConfigError, HttpError, NetworkError } from './errors.js';
|
|
30
|
+
export { HttpTransport, } from './http.js';
|
|
31
|
+
export { GitLabClient, } from './gitlab-client.js';
|
|
32
|
+
export { stripControl, stripControlOneLine } from './text.js';
|
|
33
|
+
// --- Safety: the read-only guard and credential redaction, shared by every consumer ---
|
|
34
|
+
export { assertWritesAllowed, isReadOnly, READ_ONLY_ENV } from './write-safety.js';
|
|
35
|
+
export { collectSecrets, redactSecrets, sanitiseDeep, SECRET_ENV, secretValues } from './redaction.js';
|
|
36
|
+
// --- Shared input handling and rendering ---
|
|
37
|
+
export { assertNotEmpty, mergeBody, parseBodyInput, readTextFile } from './json-input.js';
|
|
38
|
+
export { formatKeyValue, formatTable } from './output.js';
|
|
39
|
+
export { branchColumns, commitColumns, environmentColumns, jobColumns, mergeRequestColumns, projectColumns, releaseColumns, tagColumns, variableColumns, } from './tables.js';
|
|
40
|
+
// --- Operations: what each command does between parsing its input and rendering its result ---
|
|
41
|
+
export { HIDDEN, maskVariables, tailLog } from './cicd.js';
|
|
42
|
+
export { buildCommitBody, parseActionsInput, parseCommitActions } from './commits.js';
|
|
43
|
+
export { assertIid, buildMergeRequestCreateBody, buildMergeRequestUpdateBody, mergeRequestUrl, } from './merge-requests.js';
|
|
44
|
+
export { buildFileWrite, decodeFileContent, } from './repository-files.js';
|
|
45
|
+
export { ADVANCED_SCOPES, BASIC_SCOPES, needsAdvancedSearch, searchColumns } from './search.js';
|
|
46
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,gGAAgG;AAChG,+EAA+E;AAC/E,gGAAgG;AAChG,8FAA8F;AAC9F,sEAAsE;AACtE,EAAE;AACF,iGAAiG;AACjG,8FAA8F;AAC9F,+FAA+F;AAE/F,mDAAmD;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,SAAS,EACT,OAAO,GAIR,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxF,OAAO,EACL,aAAa,GAMd,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,YAAY,GAiBb,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAE9D,yFAAyF;AACzF,OAAO,EAAE,mBAAmB,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEvG,8CAA8C;AAC9C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,cAAc,EAAE,WAAW,EAA0B,MAAM,aAAa,CAAC;AAClF,OAAO,EACL,aAAa,EACb,aAAa,EACb,kBAAkB,EAClB,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,UAAU,EACV,eAAe,GAEhB,MAAM,aAAa,CAAC;AAErB,gGAAgG;AAChG,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,kBAAkB,EAAoB,MAAM,cAAc,CAAC;AACxG,OAAO,EACL,SAAS,EACT,2BAA2B,EAC3B,2BAA2B,EAC3B,eAAe,GAGhB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,cAAc,EACd,iBAAiB,GAIlB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a raw JSON request body from a flag or a file. GitLab's merge-request and commit
|
|
3
|
+
* endpoints accept far more attributes than it is worth giving a flag each — squash options,
|
|
4
|
+
* approval rules, milestone ids — and the set grows without our involvement, so an escape hatch
|
|
5
|
+
* means an unusual attribute never blocks anyone, while the typed flags keep the common case
|
|
6
|
+
* simple.
|
|
7
|
+
*
|
|
8
|
+
* Exactly one source is allowed. Accepting both and picking a winner would make a caller's
|
|
9
|
+
* mistake look like a preference.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseBodyInput(body: string | undefined, bodyFile: string | undefined): Record<string, unknown> | undefined;
|
|
12
|
+
/** Reads a file a caller named explicitly, reporting the path rather than a raw errno. */
|
|
13
|
+
export declare function readTextFile(path: string, label: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Merges typed flags over a raw body, dropping the flags the caller left unset.
|
|
16
|
+
*
|
|
17
|
+
* A template file can supply the shape while a flag overrides a single value, which is the way
|
|
18
|
+
* both are actually used together. GitLab request bodies are flat — unlike Jira's, there is no
|
|
19
|
+
* nested `fields` object — so a shallow merge is the whole rule.
|
|
20
|
+
*/
|
|
21
|
+
export declare function mergeBody(body: Record<string, unknown> | undefined, typed: Record<string, unknown>): Record<string, unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Requires at least one attribute to change.
|
|
24
|
+
*
|
|
25
|
+
* GitLab answers an empty `PUT` with 200 and an unchanged object, so without this a caller who
|
|
26
|
+
* misspelled a flag would be told the update succeeded and see nothing different.
|
|
27
|
+
*/
|
|
28
|
+
export declare function assertNotEmpty(body: Record<string, unknown>, what: string): Record<string, unknown>;
|
|
@@ -0,0 +1,84 @@
|
|
|
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 { readFileSync } from 'node:fs';
|
|
17
|
+
import { ConfigError } from './errors.js';
|
|
18
|
+
/**
|
|
19
|
+
* Reads a raw JSON request body from a flag or a file. GitLab's merge-request and commit
|
|
20
|
+
* endpoints accept far more attributes than it is worth giving a flag each — squash options,
|
|
21
|
+
* approval rules, milestone ids — and the set grows without our involvement, so an escape hatch
|
|
22
|
+
* means an unusual attribute never blocks anyone, while the typed flags keep the common case
|
|
23
|
+
* simple.
|
|
24
|
+
*
|
|
25
|
+
* Exactly one source is allowed. Accepting both and picking a winner would make a caller's
|
|
26
|
+
* mistake look like a preference.
|
|
27
|
+
*/
|
|
28
|
+
export function parseBodyInput(body, bodyFile) {
|
|
29
|
+
if (body !== undefined && bodyFile !== undefined) {
|
|
30
|
+
throw new ConfigError('Pass --body or --body-file, not both.');
|
|
31
|
+
}
|
|
32
|
+
const source = body ?? (bodyFile === undefined ? undefined : readTextFile(bodyFile, 'Body file'));
|
|
33
|
+
if (source === undefined)
|
|
34
|
+
return undefined;
|
|
35
|
+
const label = bodyFile ?? '--body';
|
|
36
|
+
let parsed;
|
|
37
|
+
try {
|
|
38
|
+
parsed = JSON.parse(source);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
// Only the position, never the parser's snippet of the content: a caller can name any path,
|
|
42
|
+
// and the snippet would echo the first bytes of whatever file that was.
|
|
43
|
+
const position = error instanceof Error ? /position \d+(?::? line \d+ column \d+)?/.exec(error.message)?.[0] : undefined;
|
|
44
|
+
throw new ConfigError(`${label} is not valid JSON${position === undefined ? '' : ` at ${position}`}.`);
|
|
45
|
+
}
|
|
46
|
+
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
47
|
+
throw new ConfigError(`${label} must be a JSON object.`);
|
|
48
|
+
}
|
|
49
|
+
return parsed;
|
|
50
|
+
}
|
|
51
|
+
/** Reads a file a caller named explicitly, reporting the path rather than a raw errno. */
|
|
52
|
+
export function readTextFile(path, label) {
|
|
53
|
+
try {
|
|
54
|
+
return readFileSync(path, 'utf8');
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
const reason = error.code === 'ENOENT' ? 'not found' : 'could not be read';
|
|
58
|
+
throw new ConfigError(`${label} ${path} ${reason}.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Merges typed flags over a raw body, dropping the flags the caller left unset.
|
|
63
|
+
*
|
|
64
|
+
* A template file can supply the shape while a flag overrides a single value, which is the way
|
|
65
|
+
* both are actually used together. GitLab request bodies are flat — unlike Jira's, there is no
|
|
66
|
+
* nested `fields` object — so a shallow merge is the whole rule.
|
|
67
|
+
*/
|
|
68
|
+
export function mergeBody(body, typed) {
|
|
69
|
+
const provided = Object.fromEntries(Object.entries(typed).filter(([, value]) => value !== undefined));
|
|
70
|
+
return { ...(body ?? {}), ...provided };
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Requires at least one attribute to change.
|
|
74
|
+
*
|
|
75
|
+
* GitLab answers an empty `PUT` with 200 and an unchanged object, so without this a caller who
|
|
76
|
+
* misspelled a flag would be told the update succeeded and see nothing different.
|
|
77
|
+
*/
|
|
78
|
+
export function assertNotEmpty(body, what) {
|
|
79
|
+
if (Object.keys(body).length === 0) {
|
|
80
|
+
throw new ConfigError(`Nothing to change: name at least one attribute to update on this ${what}.`);
|
|
81
|
+
}
|
|
82
|
+
return body;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=json-input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-input.js","sourceRoot":"","sources":["../src/json-input.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAC5B,IAAwB,EACxB,QAA4B;IAE5B,IAAI,IAAI,KAAK,SAAS,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QACjD,MAAM,IAAI,WAAW,CAAC,uCAAuC,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;IAClG,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE3C,MAAM,KAAK,GAAG,QAAQ,IAAI,QAAQ,CAAC;IACnC,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,4FAA4F;QAC5F,wEAAwE;QACxE,MAAM,QAAQ,GACZ,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,yCAAyC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1G,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,qBAAqB,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,QAAQ,EAAE,GAAG,CAAC,CAAC;IACzG,CAAC;IAED,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,yBAAyB,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,KAAa;IACtD,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,MAAM,GAAI,KAA+B,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,mBAAmB,CAAC;QACtG,MAAM,IAAI,WAAW,CAAC,GAAG,KAAK,IAAI,IAAI,IAAI,MAAM,GAAG,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,IAAyC,EACzC,KAA8B;IAE9B,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC;IACtG,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,EAAE,GAAG,QAAQ,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,IAA6B,EAAE,IAAY;IACxE,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,WAAW,CAAC,oEAAoE,IAAI,GAAG,CAAC,CAAC;IACrG,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge requests are addressed by their per-project `iid`, not the instance-wide `id`, and both
|
|
3
|
+
* are bare integers that appear side by side in every payload. Passing the wrong one does not
|
|
4
|
+
* fail: it silently addresses a different merge request in a different project that happens to
|
|
5
|
+
* share the number. So the value is validated as an iid — small, positive, whole — and the error
|
|
6
|
+
* says which of the two fields to read.
|
|
7
|
+
*/
|
|
8
|
+
export declare function assertIid(value: unknown, what?: string): number;
|
|
9
|
+
export interface MergeRequestCreateInput {
|
|
10
|
+
readonly sourceBranch?: string;
|
|
11
|
+
readonly targetBranch?: string;
|
|
12
|
+
readonly title?: string;
|
|
13
|
+
readonly description?: string;
|
|
14
|
+
readonly assigneeIds?: readonly number[];
|
|
15
|
+
readonly reviewerIds?: readonly number[];
|
|
16
|
+
readonly labels?: readonly string[];
|
|
17
|
+
readonly milestoneId?: number;
|
|
18
|
+
readonly removeSourceBranch?: boolean;
|
|
19
|
+
readonly squash?: boolean;
|
|
20
|
+
readonly draft?: boolean;
|
|
21
|
+
readonly targetProjectId?: number;
|
|
22
|
+
readonly body?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildMergeRequestCreateBody(input: MergeRequestCreateInput): Record<string, unknown>;
|
|
25
|
+
export interface MergeRequestUpdateInput {
|
|
26
|
+
readonly title?: string;
|
|
27
|
+
readonly description?: string;
|
|
28
|
+
readonly targetBranch?: string;
|
|
29
|
+
readonly assigneeIds?: readonly number[];
|
|
30
|
+
readonly reviewerIds?: readonly number[];
|
|
31
|
+
readonly labels?: readonly string[];
|
|
32
|
+
readonly milestoneId?: number;
|
|
33
|
+
readonly removeSourceBranch?: boolean;
|
|
34
|
+
readonly squash?: boolean;
|
|
35
|
+
readonly stateEvent?: 'close' | 'reopen';
|
|
36
|
+
readonly body?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
export declare function buildMergeRequestUpdateBody(input: MergeRequestUpdateInput): Record<string, unknown>;
|
|
39
|
+
/** The web URL of a created or updated merge request, when the payload carries one. */
|
|
40
|
+
export declare function mergeRequestUrl(payload: unknown): string | undefined;
|
|
@@ -0,0 +1,89 @@
|
|
|
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 { ConfigError } from './errors.js';
|
|
17
|
+
import { assertNotEmpty, mergeBody } from './json-input.js';
|
|
18
|
+
/**
|
|
19
|
+
* Merge requests are addressed by their per-project `iid`, not the instance-wide `id`, and both
|
|
20
|
+
* are bare integers that appear side by side in every payload. Passing the wrong one does not
|
|
21
|
+
* fail: it silently addresses a different merge request in a different project that happens to
|
|
22
|
+
* share the number. So the value is validated as an iid — small, positive, whole — and the error
|
|
23
|
+
* says which of the two fields to read.
|
|
24
|
+
*/
|
|
25
|
+
export function assertIid(value, what = 'merge request') {
|
|
26
|
+
const iid = typeof value === 'string' ? Number(value) : value;
|
|
27
|
+
if (typeof iid !== 'number' || !Number.isInteger(iid) || iid < 1) {
|
|
28
|
+
throw new ConfigError(`A ${what} is addressed by its project-scoped iid: a positive whole number, got ${JSON.stringify(value)}. ` +
|
|
29
|
+
'It is the number in the web URL and the "iid" field of the API payload, not "id".');
|
|
30
|
+
}
|
|
31
|
+
return iid;
|
|
32
|
+
}
|
|
33
|
+
/** GitLab marks a draft by the title prefix; there is no separate flag on the create endpoint. */
|
|
34
|
+
const DRAFT_PREFIX = 'Draft: ';
|
|
35
|
+
export function buildMergeRequestCreateBody(input) {
|
|
36
|
+
const title = input.title !== undefined && input.draft === true && !input.title.startsWith(DRAFT_PREFIX)
|
|
37
|
+
? `${DRAFT_PREFIX}${input.title}`
|
|
38
|
+
: input.title;
|
|
39
|
+
const merged = mergeBody(input.body, {
|
|
40
|
+
source_branch: input.sourceBranch,
|
|
41
|
+
target_branch: input.targetBranch,
|
|
42
|
+
title,
|
|
43
|
+
description: input.description,
|
|
44
|
+
assignee_ids: input.assigneeIds,
|
|
45
|
+
reviewer_ids: input.reviewerIds,
|
|
46
|
+
labels: input.labels === undefined ? undefined : input.labels.join(','),
|
|
47
|
+
milestone_id: input.milestoneId,
|
|
48
|
+
remove_source_branch: input.removeSourceBranch,
|
|
49
|
+
squash: input.squash,
|
|
50
|
+
target_project_id: input.targetProjectId,
|
|
51
|
+
});
|
|
52
|
+
// Checked here so the refusal names the flag. GitLab's own 400 for a missing source branch is
|
|
53
|
+
// `{"message":{"source_branch":["can't be blank"]}}`, which does not say how to supply one.
|
|
54
|
+
for (const [key, flag] of [
|
|
55
|
+
['source_branch', '--source-branch'],
|
|
56
|
+
['target_branch', '--target-branch'],
|
|
57
|
+
['title', '--title'],
|
|
58
|
+
]) {
|
|
59
|
+
if (typeof merged[key] !== 'string' || merged[key] === '') {
|
|
60
|
+
throw new ConfigError(`A ${key.replace('_', ' ')} is required: pass ${flag}.`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return merged;
|
|
64
|
+
}
|
|
65
|
+
export function buildMergeRequestUpdateBody(input) {
|
|
66
|
+
return assertNotEmpty(mergeBody(input.body, {
|
|
67
|
+
title: input.title,
|
|
68
|
+
description: input.description,
|
|
69
|
+
target_branch: input.targetBranch,
|
|
70
|
+
assignee_ids: input.assigneeIds,
|
|
71
|
+
reviewer_ids: input.reviewerIds,
|
|
72
|
+
// Deliberately `labels`, which replaces the whole set, rather than add_labels/remove_labels.
|
|
73
|
+
// One verb per flag: a flag that sometimes adds and sometimes replaces is the kind of thing
|
|
74
|
+
// a caller discovers by losing labels.
|
|
75
|
+
labels: input.labels === undefined ? undefined : input.labels.join(','),
|
|
76
|
+
milestone_id: input.milestoneId,
|
|
77
|
+
remove_source_branch: input.removeSourceBranch,
|
|
78
|
+
squash: input.squash,
|
|
79
|
+
state_event: input.stateEvent,
|
|
80
|
+
}), 'merge request');
|
|
81
|
+
}
|
|
82
|
+
/** The web URL of a created or updated merge request, when the payload carries one. */
|
|
83
|
+
export function mergeRequestUrl(payload) {
|
|
84
|
+
if (typeof payload !== 'object' || payload === null)
|
|
85
|
+
return undefined;
|
|
86
|
+
const url = payload.web_url;
|
|
87
|
+
return typeof url === 'string' ? url : undefined;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=merge-requests.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-requests.js","sourceRoot":"","sources":["../src/merge-requests.ts"],"names":[],"mappings":"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"}
|
package/lib/output.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** One labelled value in a detail view. `undefined` values are dropped, not printed as blanks. */
|
|
2
|
+
export type Pair = readonly [label: string, value: unknown];
|
|
3
|
+
export interface Column<Row> {
|
|
4
|
+
readonly header: string;
|
|
5
|
+
readonly value: (row: Row) => unknown;
|
|
6
|
+
}
|
|
7
|
+
export { stripControl, stripControlOneLine } from './text.js';
|
|
8
|
+
/** Aligned `Label: value` lines for a single entity. */
|
|
9
|
+
export declare function formatKeyValue(pairs: readonly Pair[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Two-space-separated columns with a header rule. Every column is sized to its widest cell, and
|
|
12
|
+
* the last column is never padded so trailing whitespace never lands in a pipeline.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatTable<Row>(rows: readonly Row[], columns: ReadonlyArray<Column<Row>>): string;
|