@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.
Files changed (59) hide show
  1. package/README.md +76 -0
  2. package/lib/auth.d.ts +9 -0
  3. package/lib/auth.js +26 -0
  4. package/lib/auth.js.map +1 -0
  5. package/lib/cicd.d.ts +29 -0
  6. package/lib/cicd.js +56 -0
  7. package/lib/cicd.js.map +1 -0
  8. package/lib/commits.d.ts +34 -0
  9. package/lib/commits.js +101 -0
  10. package/lib/commits.js.map +1 -0
  11. package/lib/config.d.ts +21 -0
  12. package/lib/config.js +76 -0
  13. package/lib/config.js.map +1 -0
  14. package/lib/env-file.d.ts +11 -0
  15. package/lib/env-file.js +98 -0
  16. package/lib/env-file.js.map +1 -0
  17. package/lib/errors.d.ts +31 -0
  18. package/lib/errors.js +66 -0
  19. package/lib/errors.js.map +1 -0
  20. package/lib/gitlab-client.d.ts +137 -0
  21. package/lib/gitlab-client.js +250 -0
  22. package/lib/gitlab-client.js.map +1 -0
  23. package/lib/http.d.ts +82 -0
  24. package/lib/http.js +285 -0
  25. package/lib/http.js.map +1 -0
  26. package/lib/index.d.ts +17 -0
  27. package/lib/index.js +46 -0
  28. package/lib/index.js.map +1 -0
  29. package/lib/json-input.d.ts +28 -0
  30. package/lib/json-input.js +84 -0
  31. package/lib/json-input.js.map +1 -0
  32. package/lib/merge-requests.d.ts +40 -0
  33. package/lib/merge-requests.js +89 -0
  34. package/lib/merge-requests.js.map +1 -0
  35. package/lib/output.d.ts +14 -0
  36. package/lib/output.js +65 -0
  37. package/lib/output.js.map +1 -0
  38. package/lib/redaction.d.ts +26 -0
  39. package/lib/redaction.js +71 -0
  40. package/lib/redaction.js.map +1 -0
  41. package/lib/repository-files.d.ts +50 -0
  42. package/lib/repository-files.js +70 -0
  43. package/lib/repository-files.js.map +1 -0
  44. package/lib/search.d.ts +20 -0
  45. package/lib/search.js +92 -0
  46. package/lib/search.js.map +1 -0
  47. package/lib/tables.d.ts +21 -0
  48. package/lib/tables.js +105 -0
  49. package/lib/tables.js.map +1 -0
  50. package/lib/testing.d.ts +16 -0
  51. package/lib/testing.js +63 -0
  52. package/lib/testing.js.map +1 -0
  53. package/lib/text.d.ts +10 -0
  54. package/lib/text.js +80 -0
  55. package/lib/text.js.map +1 -0
  56. package/lib/write-safety.d.ts +19 -0
  57. package/lib/write-safety.js +45 -0
  58. package/lib/write-safety.js.map +1 -0
  59. package/package.json +130 -0
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @simplysf/simply-gitlab-core
2
+
3
+ Configuration, authentication, the HTTP client, and the shared logic that
4
+ [`@simplysf/simply-gitlab`](../simply-gitlab) (the CLI) and
5
+ [`@simplysf/simply-gitlab-mcp`](../simply-gitlab-mcp) (the MCP server) are both built on.
6
+
7
+ Everything a command does between parsing its input and rendering its result lives here, so the CLI
8
+ and the MCP server cannot disagree about what a rule means. Nothing in this package touches a
9
+ terminal or a process: no oclif, no child processes, no `process.argv`, nothing written to stdout or
10
+ stderr. `process.env` is read only through an injectable `env` parameter.
11
+
12
+ You want this package directly if you are building something else against GitLab in TypeScript. If
13
+ you want a CLI or an MCP server, install one of those instead.
14
+
15
+ ```sh
16
+ npm install @simplysf/simply-gitlab-core
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ts
22
+ import { GitLabClient, resolveGitLabConfig } from '@simplysf/simply-gitlab-core';
23
+
24
+ // Reads GITLAB_URL (default https://gitlab.com) and GITLAB_TOKEN, and throws a ConfigError
25
+ // naming what is missing.
26
+ const client = new GitLabClient(resolveGitLabConfig());
27
+
28
+ // A project is named by numeric id or by full path; the path is encoded for you.
29
+ const project = await client.getProject('group/subgroup/project');
30
+
31
+ // Lists follow pages up to your limit and tell you whether they got everything.
32
+ const { items, total, complete } = await client.listMergeRequests('group/project', { state: 'opened' }, 50);
33
+ if (!complete) console.warn(`showing ${items.length} of ${total ?? 'many'}`);
34
+ ```
35
+
36
+ ## What is in here
37
+
38
+ **Configuration and auth** — `resolveGitLabConfig`, `buildAuthHeaders`, `loadEnvFile`.
39
+
40
+ **Transport** — `HttpTransport`, with retries on 429 and 5xx, a capped `Retry-After`, a 30s deadline
41
+ covering the response body, GitLab's bracketed array query parameters, a non-JSON mode for job
42
+ traces, and its offset-pagination headers.
43
+
44
+ **The client** — `GitLabClient`, which owns project-path encoding and page walking, and returns
45
+ `{ items, total?, pages, complete }` from every list.
46
+
47
+ **Errors** — `ConfigError` (exit 2), `AuthError` (3), `NetworkError` (1), `HttpError` (1), all
48
+ extending `CliError`.
49
+
50
+ **Safety** — `assertWritesAllowed`/`isReadOnly` for `GITLAB_READ_ONLY`; `redactSecrets` and
51
+ `sanitiseDeep` to keep a token out of any message or response body; `stripControl` and
52
+ `stripControlOneLine` for text the instance chose.
53
+
54
+ **Operations** — `assertIid`, `buildMergeRequestCreateBody`, `buildMergeRequestUpdateBody`,
55
+ `buildCommitBody`, `parseCommitActions`, `buildFileWrite`, `decodeFileContent`, `maskVariables`,
56
+ `tailLog`, `searchColumns`.
57
+
58
+ **Rendering** — `formatTable`, `formatKeyValue`, and the column sets each list command uses.
59
+
60
+ **Testing** — `@simplysf/simply-gitlab-core/testing` exports `startTestServer` and `respondJson`, a
61
+ tiny local HTTP server that records every request. Both other packages test against it.
62
+
63
+ ## Stability
64
+
65
+ Every export from `src/index.ts` is semver-covered: adding one is a minor or patch change, removing
66
+ or renaming one is breaking. `test/index.test.ts` pins the list, so a removal fails a test rather
67
+ than shipping quietly in a patch.
68
+
69
+ ## Documentation
70
+
71
+ - [GitLab client core design](../../docs/design/0001-gitlab-client-core.md)
72
+ - [Contributing](./CONTRIBUTING.md)
73
+
74
+ ## License
75
+
76
+ Apache-2.0.
package/lib/auth.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ import type { GitLabConfig } from './config.js';
2
+ /**
3
+ * GitLab accepts the same token as `PRIVATE-TOKEN` or as an OAuth bearer, and the two are not
4
+ * interchangeable: a personal, project, or group access token authenticates only through
5
+ * `PRIVATE-TOKEN`, while `Authorization: Bearer` is reserved for OAuth 2 tokens. This CLI issues
6
+ * neither OAuth flow, so it sends the header that works for every token a user can create by
7
+ * hand. Returns just the header so callers can merge it into a request.
8
+ */
9
+ export declare function buildAuthHeaders(config: GitLabConfig): Record<string, string>;
package/lib/auth.js ADDED
@@ -0,0 +1,26 @@
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
+ * GitLab accepts the same token as `PRIVATE-TOKEN` or as an OAuth bearer, and the two are not
18
+ * interchangeable: a personal, project, or group access token authenticates only through
19
+ * `PRIVATE-TOKEN`, while `Authorization: Bearer` is reserved for OAuth 2 tokens. This CLI issues
20
+ * neither OAuth flow, so it sends the header that works for every token a user can create by
21
+ * hand. Returns just the header so callers can merge it into a request.
22
+ */
23
+ export function buildAuthHeaders(config) {
24
+ return { 'PRIVATE-TOKEN': config.token };
25
+ }
26
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAoB;IACnD,OAAO,EAAE,eAAe,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AAC3C,CAAC"}
package/lib/cicd.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /** What a hidden CI variable's value is replaced with, in both the table and the JSON. */
2
+ export declare const HIDDEN = "<hidden>";
3
+ /**
4
+ * Replaces the value of every CI/CD variable with a placeholder.
5
+ *
6
+ * `GET /projects/:id/variables` returns every variable's value in clear text — including the ones
7
+ * marked "masked", because masking only applies to job logs. Those values are deploy keys, signing
8
+ * certificates, and production credentials, and the endpoint's usual caller is a person listing
9
+ * variables to see *which* exist, not to read them.
10
+ *
11
+ * So the values are hidden by default and revealed only on an explicit request. This is the one
12
+ * place the CLI and the MCP server deliberately differ: `--reveal` exists on the command, and
13
+ * there is no equivalent tool input, because a value revealed to an agent is a value copied into
14
+ * a context window, a transcript, and whatever the agent writes next. A person who needs the
15
+ * value can run the command; nothing about the tool catalogue should make exfiltrating a
16
+ * production secret a one-call operation.
17
+ */
18
+ export declare function maskVariables(items: readonly unknown[]): unknown[];
19
+ /**
20
+ * Trims a job trace to its last `lines` lines.
21
+ *
22
+ * A trace is regularly tens of megabytes, and the interesting part of a failed job is at the end.
23
+ * Returning the whole thing by default would blow a terminal's scrollback and an agent's context
24
+ * for a question that is almost always "why did it fail".
25
+ */
26
+ export declare function tailLog(log: string, lines: number): {
27
+ readonly text: string;
28
+ readonly truncated: boolean;
29
+ };
package/lib/cicd.js ADDED
@@ -0,0 +1,56 @@
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
+ /** What a hidden CI variable's value is replaced with, in both the table and the JSON. */
17
+ export const HIDDEN = '<hidden>';
18
+ /**
19
+ * Replaces the value of every CI/CD variable with a placeholder.
20
+ *
21
+ * `GET /projects/:id/variables` returns every variable's value in clear text — including the ones
22
+ * marked "masked", because masking only applies to job logs. Those values are deploy keys, signing
23
+ * certificates, and production credentials, and the endpoint's usual caller is a person listing
24
+ * variables to see *which* exist, not to read them.
25
+ *
26
+ * So the values are hidden by default and revealed only on an explicit request. This is the one
27
+ * place the CLI and the MCP server deliberately differ: `--reveal` exists on the command, and
28
+ * there is no equivalent tool input, because a value revealed to an agent is a value copied into
29
+ * a context window, a transcript, and whatever the agent writes next. A person who needs the
30
+ * value can run the command; nothing about the tool catalogue should make exfiltrating a
31
+ * production secret a one-call operation.
32
+ */
33
+ export function maskVariables(items) {
34
+ return items.map((item) => {
35
+ if (typeof item !== 'object' || item === null)
36
+ return item;
37
+ const variable = item;
38
+ return 'value' in variable ? { ...variable, value: HIDDEN } : variable;
39
+ });
40
+ }
41
+ /**
42
+ * Trims a job trace to its last `lines` lines.
43
+ *
44
+ * A trace is regularly tens of megabytes, and the interesting part of a failed job is at the end.
45
+ * Returning the whole thing by default would blow a terminal's scrollback and an agent's context
46
+ * for a question that is almost always "why did it fail".
47
+ */
48
+ export function tailLog(log, lines) {
49
+ if (!Number.isInteger(lines) || lines < 1)
50
+ return { text: log, truncated: false };
51
+ const all = log.split('\n');
52
+ if (all.length <= lines)
53
+ return { text: log, truncated: false };
54
+ return { text: all.slice(-lines).join('\n'), truncated: true };
55
+ }
56
+ //# sourceMappingURL=cicd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cicd.js","sourceRoot":"","sources":["../src/cicd.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,0FAA0F;AAC1F,MAAM,CAAC,MAAM,MAAM,GAAG,UAAU,CAAC;AAEjC;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,aAAa,CAAC,KAAyB;IACrD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACxB,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC3D,MAAM,QAAQ,GAAG,IAA+B,CAAC;QACjD,OAAO,OAAO,IAAI,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IACzE,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,KAAa;IAChD,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAClF,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,GAAG,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAChE,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;AACjE,CAAC"}
@@ -0,0 +1,34 @@
1
+ import type { CommitAction } from './gitlab-client.js';
2
+ /**
3
+ * Validates the `actions` array of a commit.
4
+ *
5
+ * GitLab answers a malformed action with a 400 whose body names neither the index nor the file,
6
+ * so a ten-file commit with one bad entry becomes a hunt. Every rule checked here is one GitLab
7
+ * itself enforces — this only moves the report to where the caller can act on it.
8
+ */
9
+ export declare function parseCommitActions(value: unknown): readonly CommitAction[];
10
+ export interface CommitInput {
11
+ readonly branch?: string;
12
+ readonly message?: string;
13
+ readonly actions?: unknown;
14
+ readonly startBranch?: string;
15
+ readonly startSha?: string;
16
+ readonly authorEmail?: string;
17
+ readonly authorName?: string;
18
+ readonly force?: boolean;
19
+ readonly stats?: boolean;
20
+ readonly body?: Record<string, unknown>;
21
+ }
22
+ /**
23
+ * Builds the body of a multi-file commit. Typed properties are merged over `body`, so a template
24
+ * file can supply the action list while a flag names the branch.
25
+ */
26
+ export declare function buildCommitBody(input: CommitInput): Record<string, unknown>;
27
+ /**
28
+ * Parses the `--actions` flag.
29
+ *
30
+ * `JSON.parse` on its own raises a SyntaxError carrying a snippet of the input, which for a flag
31
+ * a caller may have built from a file would echo that file's first bytes into the error. Only the
32
+ * position is kept, matching how `parseBodyInput` handles the same risk.
33
+ */
34
+ export declare function parseActionsInput(raw: string | undefined): unknown;
package/lib/commits.js ADDED
@@ -0,0 +1,101 @@
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 { mergeBody } from './json-input.js';
18
+ const ACTIONS = new Set(['create', 'delete', 'move', 'update', 'chmod']);
19
+ /** What each action needs beyond `file_path`, checked before the request rather than after. */
20
+ const REQUIRED = {
21
+ create: ['content'],
22
+ update: ['content'],
23
+ move: ['previous_path'],
24
+ delete: [],
25
+ chmod: ['execute_filemode'],
26
+ };
27
+ /**
28
+ * Validates the `actions` array of a commit.
29
+ *
30
+ * GitLab answers a malformed action with a 400 whose body names neither the index nor the file,
31
+ * so a ten-file commit with one bad entry becomes a hunt. Every rule checked here is one GitLab
32
+ * itself enforces — this only moves the report to where the caller can act on it.
33
+ */
34
+ export function parseCommitActions(value) {
35
+ if (!Array.isArray(value) || value.length === 0) {
36
+ throw new ConfigError('A commit needs at least one action, as a JSON array under "actions".');
37
+ }
38
+ return value.map((raw, index) => {
39
+ const where = `actions[${index}]`;
40
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
41
+ throw new ConfigError(`${where} must be a JSON object.`);
42
+ }
43
+ const action = raw;
44
+ const verb = action.action;
45
+ if (typeof verb !== 'string' || !ACTIONS.has(verb)) {
46
+ throw new ConfigError(`${where}.action must be one of ${[...ACTIONS].join(', ')}.`);
47
+ }
48
+ if (typeof action.file_path !== 'string' || action.file_path === '') {
49
+ throw new ConfigError(`${where}.file_path is required.`);
50
+ }
51
+ for (const key of REQUIRED[verb] ?? []) {
52
+ if (action[key] === undefined) {
53
+ throw new ConfigError(`${where}.${key} is required for the "${verb}" action.`);
54
+ }
55
+ }
56
+ return action;
57
+ });
58
+ }
59
+ /**
60
+ * Builds the body of a multi-file commit. Typed properties are merged over `body`, so a template
61
+ * file can supply the action list while a flag names the branch.
62
+ */
63
+ export function buildCommitBody(input) {
64
+ const merged = mergeBody(input.body, {
65
+ branch: input.branch,
66
+ commit_message: input.message,
67
+ actions: input.actions,
68
+ start_branch: input.startBranch,
69
+ start_sha: input.startSha,
70
+ author_email: input.authorEmail,
71
+ author_name: input.authorName,
72
+ force: input.force,
73
+ stats: input.stats,
74
+ });
75
+ if (typeof merged.branch !== 'string' || merged.branch === '') {
76
+ throw new ConfigError('A branch is required: pass --branch.');
77
+ }
78
+ if (typeof merged.commit_message !== 'string' || merged.commit_message === '') {
79
+ throw new ConfigError('A commit message is required: pass --message.');
80
+ }
81
+ return { ...merged, actions: parseCommitActions(merged.actions) };
82
+ }
83
+ /**
84
+ * Parses the `--actions` flag.
85
+ *
86
+ * `JSON.parse` on its own raises a SyntaxError carrying a snippet of the input, which for a flag
87
+ * a caller may have built from a file would echo that file's first bytes into the error. Only the
88
+ * position is kept, matching how `parseBodyInput` handles the same risk.
89
+ */
90
+ export function parseActionsInput(raw) {
91
+ if (raw === undefined)
92
+ return undefined;
93
+ try {
94
+ return JSON.parse(raw);
95
+ }
96
+ catch (error) {
97
+ const position = error instanceof Error ? /position \d+(?::? line \d+ column \d+)?/.exec(error.message)?.[0] : undefined;
98
+ throw new ConfigError(`--actions is not valid JSON${position === undefined ? '' : ` at ${position}`}.`);
99
+ }
100
+ }
101
+ //# sourceMappingURL=commits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commits.js","sourceRoot":"","sources":["../src/commits.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface GitLabConfig {
2
+ /** Instance origin, with any trailing path preserved for instances served under a subpath. */
3
+ readonly url: string;
4
+ /** Personal, project, or group access token, sent as `PRIVATE-TOKEN`. */
5
+ readonly token: string;
6
+ }
7
+ /** Per-invocation overrides, sourced from global flags. Anything set here beats the environment. */
8
+ export interface ConfigOverrides {
9
+ readonly url?: string;
10
+ readonly token?: string;
11
+ }
12
+ export type EnvLike = Record<string, string | undefined>;
13
+ export declare const URL_ENV = "GITLAB_URL";
14
+ export declare const TOKEN_ENV = "GITLAB_TOKEN";
15
+ /**
16
+ * Where an unconfigured `GITLAB_URL` points. GitLab is one product with one API, and the great
17
+ * majority of tokens are gitlab.com tokens, so requiring the URL would be ceremony for the common
18
+ * case. A self-managed instance sets the variable.
19
+ */
20
+ export declare const DEFAULT_URL = "https://gitlab.com";
21
+ export declare function resolveGitLabConfig(overrides?: ConfigOverrides, env?: EnvLike): GitLabConfig;
package/lib/config.js ADDED
@@ -0,0 +1,76 @@
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 process from 'node:process';
17
+ import { ConfigError } from './errors.js';
18
+ export const URL_ENV = 'GITLAB_URL';
19
+ export const TOKEN_ENV = 'GITLAB_TOKEN';
20
+ const SSL_VERIFY_ENV = 'GITLAB_SSL_VERIFY';
21
+ /**
22
+ * Where an unconfigured `GITLAB_URL` points. GitLab is one product with one API, and the great
23
+ * majority of tokens are gitlab.com tokens, so requiring the URL would be ceremony for the common
24
+ * case. A self-managed instance sets the variable.
25
+ */
26
+ export const DEFAULT_URL = 'https://gitlab.com';
27
+ const FALSE_VALUES = new Set(['false', '0', 'no', 'off']);
28
+ const CA_BUNDLE_HINT = 'NODE_EXTRA_CA_CERTS=/path/to/ca.pem';
29
+ /**
30
+ * Parses the configured base URL. Unlike Atlassian's two deployments, every GitLab instance
31
+ * speaks the same `/api/v4` surface, so the only normalization needed is trimming a trailing
32
+ * slash — and keeping any subpath, because self-managed instances are routinely served from one
33
+ * (`https://example.com/gitlab`).
34
+ */
35
+ function normalizeUrl(rawUrl) {
36
+ let parsed;
37
+ try {
38
+ parsed = new URL(rawUrl);
39
+ }
40
+ catch {
41
+ throw new ConfigError(`GitLab URL is not a valid URL: ${rawUrl}. Set ${URL_ENV} to something like https://gitlab.example.com.`);
42
+ }
43
+ if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
44
+ throw new ConfigError(`GitLab URL must use http or https, got ${parsed.protocol} (${URL_ENV}).`);
45
+ }
46
+ // A pasted project URL — https://gitlab.com/group/project — would otherwise be treated as an
47
+ // instance served under /group/project, and every request would 404 with nothing pointing at
48
+ // the cause. gitlab.com has no subpath, so the path is noise there by construction.
49
+ if (parsed.hostname.toLowerCase() === 'gitlab.com')
50
+ return parsed.origin;
51
+ return `${parsed.origin}${parsed.pathname.replace(/\/+$/, '')}`;
52
+ }
53
+ /**
54
+ * Certificate verification is not negotiable, so a `GITLAB_SSL_VERIFY=false` carried over from
55
+ * other GitLab tooling fails loudly rather than being silently ignored — the alternative is a
56
+ * user believing verification is off when it is on, or vice versa. The fix keeps verification
57
+ * intact.
58
+ */
59
+ function rejectSslOptOut(env) {
60
+ const raw = env[SSL_VERIFY_ENV];
61
+ if (raw !== undefined && FALSE_VALUES.has(raw.trim().toLowerCase())) {
62
+ throw new ConfigError(`${SSL_VERIFY_ENV}=${raw} is not supported: this CLI always verifies TLS certificates. ` +
63
+ `If your GitLab instance is behind an internal or agency CA, trust that CA instead with ${CA_BUNDLE_HINT}.`);
64
+ }
65
+ }
66
+ export function resolveGitLabConfig(overrides = {}, env = process.env) {
67
+ rejectSslOptOut(env);
68
+ const rawUrl = (overrides.url ?? env[URL_ENV])?.trim();
69
+ const token = (overrides.token ?? env[TOKEN_ENV])?.trim();
70
+ if (!token) {
71
+ throw new ConfigError(`GitLab authentication requires a token. Set ${TOKEN_ENV} or pass --gitlab-token. ` +
72
+ 'Create one under Preferences > Access tokens with the api scope (read_api is enough for read-only use).');
73
+ }
74
+ return { url: normalizeUrl(rawUrl === undefined || rawUrl === '' ? DEFAULT_URL : rawUrl), token };
75
+ }
76
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAiB1C,MAAM,CAAC,MAAM,OAAO,GAAG,YAAY,CAAC;AACpC,MAAM,CAAC,MAAM,SAAS,GAAG,cAAc,CAAC;AACxC,MAAM,cAAc,GAAG,mBAAmB,CAAC;AAE3C;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAAC;AAEhD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;AAC1D,MAAM,cAAc,GAAG,qCAAqC,CAAC;AAE7D;;;;;GAKG;AACH,SAAS,YAAY,CAAC,MAAc;IAClC,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,WAAW,CACnB,kCAAkC,MAAM,SAAS,OAAO,gDAAgD,CACzG,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChE,MAAM,IAAI,WAAW,CAAC,0CAA0C,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,CAAC;IACnG,CAAC;IAED,6FAA6F;IAC7F,6FAA6F;IAC7F,oFAAoF;IACpF,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,YAAY;QAAE,OAAO,MAAM,CAAC,MAAM,CAAC;IAEzE,OAAO,GAAG,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;AAClE,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,GAAY;IACnC,MAAM,GAAG,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC;IAChC,IAAI,GAAG,KAAK,SAAS,IAAI,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QACpE,MAAM,IAAI,WAAW,CACnB,GAAG,cAAc,IAAI,GAAG,gEAAgE;YACtF,0FAA0F,cAAc,GAAG,CAC9G,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,YAA6B,EAAE,EAAE,MAAe,OAAO,CAAC,GAAG;IAC7F,eAAe,CAAC,GAAG,CAAC,CAAC;IAErB,MAAM,MAAM,GAAG,CAAC,SAAS,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IACvD,MAAM,KAAK,GAAG,CAAC,SAAS,CAAC,KAAK,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC;IAE1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,WAAW,CACnB,+CAA+C,SAAS,2BAA2B;YACjF,yGAAyG,CAC5G,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,GAAG,EAAE,YAAY,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC;AACpG,CAAC"}
@@ -0,0 +1,11 @@
1
+ /** Parses `.env` contents into pairs, ignoring blanks, comments, and malformed lines. */
2
+ export declare function parseEnvFile(contents: string): Record<string, string>;
3
+ /**
4
+ * Loads a `.env` file into `env`, leaving any variable that is already set untouched — the real
5
+ * environment and explicit flags outrank the file. A path the user named explicitly but that
6
+ * cannot be read is a `ConfigError`: silently continuing would leave the caller (often an agent)
7
+ * guessing why its credentials vanished.
8
+ *
9
+ * Only GitLab connection variables are applied; every other entry in the file is ignored.
10
+ */
11
+ export declare function loadEnvFile(path: string, env?: NodeJS.ProcessEnv): void;
@@ -0,0 +1,98 @@
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 process from 'node:process';
18
+ import { ConfigError } from './errors.js';
19
+ /**
20
+ * The only variables a `.env` file may set. Without this, a file the user did not write — one
21
+ * from a repo, a shared drive, or generated by an agent — could set `NODE_TLS_REJECT_UNAUTHORIZED`
22
+ * and turn off the certificate verification this CLI otherwise guarantees, handing the token to
23
+ * whoever answers. `GITLAB_SSL_VERIFY` is deliberately included so that setting it still produces the
24
+ * explanatory ConfigError rather than silence.
25
+ */
26
+ const APPLICABLE_KEYS = new Set([
27
+ // Safe to allow: the real environment still wins, so a file can turn the guard on but never
28
+ // off. A read-only credential file carrying this is the intended arrangement.
29
+ 'GITLAB_READ_ONLY',
30
+ 'GITLAB_URL',
31
+ 'GITLAB_TOKEN',
32
+ 'GITLAB_SSL_VERIFY',
33
+ ]);
34
+ /** Matches `KEY=value`, tolerating a leading `export` and surrounding whitespace. */
35
+ const ASSIGNMENT = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
36
+ /**
37
+ * Strips one layer of matching quotes. Only double quotes get escape processing, matching how
38
+ * shells treat them — `\n` in a single-quoted value is two literal characters.
39
+ */
40
+ function unquote(raw) {
41
+ const value = raw.trim();
42
+ const quote = value.startsWith('"') ? '"' : value.startsWith("'") ? "'" : undefined;
43
+ if (quote !== undefined) {
44
+ // Anything after the closing quote is a comment, so a quoted value keeps its content even
45
+ // when the line ends in `# note` — otherwise the quotes end up inside the credential.
46
+ const closingAt = value.indexOf(quote, 1);
47
+ if (closingAt !== -1) {
48
+ const inner = value.slice(1, closingAt);
49
+ // Only double quotes carry escapes, matching how a shell treats them.
50
+ return quote === '"' ? inner.replaceAll('\\n', '\n').replaceAll('\\"', '"') : inner;
51
+ }
52
+ // Unterminated quote: fall through and treat the rest as a plain value.
53
+ }
54
+ // Unquoted values stop at an inline comment: `TOKEN=abc # note` is the token `abc`.
55
+ const commentAt = value.search(/\s#/);
56
+ return (commentAt === -1 ? value : value.slice(0, commentAt)).trim();
57
+ }
58
+ /** Parses `.env` contents into pairs, ignoring blanks, comments, and malformed lines. */
59
+ export function parseEnvFile(contents) {
60
+ const parsed = {};
61
+ // An editor-written .env can start with a BOM, which would otherwise become part of the key.
62
+ for (const line of contents.replace(/^\uFEFF/, '').split(/\r?\n/)) {
63
+ if (line.trim() === '' || line.trimStart().startsWith('#'))
64
+ continue;
65
+ const match = ASSIGNMENT.exec(line);
66
+ if (match?.[1] !== undefined)
67
+ parsed[match[1]] = unquote(match[2] ?? '');
68
+ }
69
+ return parsed;
70
+ }
71
+ /**
72
+ * Loads a `.env` file into `env`, leaving any variable that is already set untouched — the real
73
+ * environment and explicit flags outrank the file. A path the user named explicitly but that
74
+ * cannot be read is a `ConfigError`: silently continuing would leave the caller (often an agent)
75
+ * guessing why its credentials vanished.
76
+ *
77
+ * Only GitLab connection variables are applied; every other entry in the file is ignored.
78
+ */
79
+ export function loadEnvFile(path, env = process.env) {
80
+ let contents;
81
+ try {
82
+ contents = readFileSync(path, 'utf8');
83
+ }
84
+ catch (error) {
85
+ const reason = error.code === 'ENOENT' ? 'not found' : 'could not be read';
86
+ throw new ConfigError(`Env file ${path} ${reason}.`);
87
+ }
88
+ for (const [key, value] of Object.entries(parseEnvFile(contents))) {
89
+ if (!APPLICABLE_KEYS.has(key))
90
+ continue;
91
+ // A variable exported as the empty string carries no value, so it must not outrank the
92
+ // file the user explicitly named — otherwise `export GITLAB_TOKEN=` in a wrapper script
93
+ // silently discards the real token and the run fails as if nothing were configured.
94
+ if (env[key] === undefined || env[key] === '')
95
+ env[key] = value;
96
+ }
97
+ }
98
+ //# sourceMappingURL=env-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-file.js","sourceRoot":"","sources":["../src/env-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Base class for every error this CLI raises deliberately. Commands surface these through
3
+ * oclif's `this.error(message, { exit })` so users get formatted output rather than a stack
4
+ * trace for expected failure modes.
5
+ */
6
+ export declare class CliError extends Error {
7
+ readonly exitCode: number;
8
+ constructor(message: string, exitCode?: number);
9
+ }
10
+ /** Missing, malformed, or self-contradictory configuration. */
11
+ export declare class ConfigError extends CliError {
12
+ constructor(message: string);
13
+ }
14
+ /**
15
+ * The instance rejected our credentials. Carries the status because 401 and 403 call for
16
+ * different responses: bad credentials versus a valid account without the needed permission.
17
+ */
18
+ export declare class AuthError extends CliError {
19
+ readonly status: number;
20
+ constructor(message: string, status: number);
21
+ }
22
+ /** The instance never answered: timeout, DNS miss, refused connection, or an untrusted certificate. */
23
+ export declare class NetworkError extends CliError {
24
+ constructor(message: string);
25
+ }
26
+ /** Any other non-2xx response, carrying the status and whatever body came back. */
27
+ export declare class HttpError extends CliError {
28
+ readonly status: number;
29
+ readonly body: unknown;
30
+ constructor(message: string, status: number, body?: unknown);
31
+ }