@zereight/mcp-gitlab 2.2.0 → 2.2.2

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.zh-CN.md CHANGED
@@ -17,7 +17,7 @@
17
17
 
18
18
  ### 为什么使用这个 GitLab MCP?
19
19
 
20
- - **267 个工具 + `discover_tools`** — 从小型 toolset 开始,运行时按需激活类别
20
+ - **274 个工具:toolset 中 272 个 + `execute_graphql` + `discover_tools`** — 从小型 toolset 开始,运行时按需激活类别
21
21
  - **MR 两步审查** — `list_merge_request_changed_files` → 批量 `get_merge_request_file_diff`
22
22
  - **内置 Agent Skill** — `skills/gitlab-mcp/` 工作流指南
23
23
  - **认证灵活** — Personal Access Token、本地 OAuth2 浏览器流程、MCP OAuth 代理、按请求远程授权
@@ -31,7 +31,7 @@
31
31
  | | @zereight/mcp-gitlab | GitLab MCP A(社区 CQRS 型) |
32
32
  |---|----------------------|------------------------------|
33
33
  | **更适合** | AI 代理工作流 | 企业多实例 / 分组工具 |
34
- | **工具模型** | ~267 个细粒度工具 + `discover_tools` | ~50–60 个 `browse_*` / `manage_*` 分组工具 |
34
+ | **工具模型** | 274 个工具:toolset 中 272 个 + `execute_graphql` + `discover_tools` | ~50–60 个 `browse_*` / `manage_*` 分组工具 |
35
35
  | **MR 审查** | 两步批量 diff | 因服务器而异 |
36
36
  | **Node.js** | >=18.17 | 通常 >=24 |
37
37
  | **许可证** | MIT | 因服务器而异 |
@@ -111,7 +111,7 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
111
111
 
112
112
  示例使用 `zereight-mcp-gitlab`,这是比旧的 `mcp-gitlab` 更不容易冲突的别名。如果 MCP 客户端找不到它,请使用 `which zereight-mcp-gitlab` 输出的绝对路径。
113
113
 
114
- 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.1.70`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
114
+ 如果不想全局安装,请将 `npx` 固定到上一个稳定版本(即文档推荐的版本),例如 `npx -y @zereight/mcp-gitlab@2.2.1`。如果始终想使用最新版本,请改用 `npx -y @zereight/mcp-gitlab@latest`。有新版本发布时,服务器会在启动时通过 stderr 提示(可用 `GITLAB_DISABLE_VERSION_CHECK=true` 关闭)。
115
115
 
116
116
  #### 使用 CLI 参数(适用于环境变量有问题的客户端)
117
117
 
@@ -133,13 +133,13 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
133
133
 
134
134
  - `--token` - GitLab Personal Access Token(替代 `GITLAB_PERSONAL_ACCESS_TOKEN`)
135
135
  - `--api-url` - GitLab API URL(替代 `GITLAB_API_URL`)
136
- - `--read-only=true` - 启用只读模式(替代 `GITLAB_READ_ONLY_MODE`,已弃用 — 推荐 `--permission-mode=readonly`)
136
+ - `--read-only=true` - Deprecated (removed in next major (v3.0.0));请改用 `--permission-mode=readonly`(替代 `GITLAB_READ_ONLY_MODE`)
137
137
  - `--permission-mode` - 权限级别:`readonly`、`modify`(禁用删除/拆除工具)或 `full`(替代 `GITLAB_PERMISSION_MODE`,默认 `full`)
138
138
  - `--toolsets=all` - 启用指定工具集(替代 `GITLAB_TOOLSETS`;未设置时使用精简 `core` 默认值)
139
139
  - `--tools=list_issues` - 添加单个工具(替代 `GITLAB_TOOLS`)
140
- - `--use-wiki=true` - 启用 Wiki API(替代 `USE_GITLAB_WIKI`,旧版 — 推荐 `GITLAB_TOOLSETS=wiki`)
141
- - `--use-milestone=true` - 启用里程碑 API(替代 `USE_MILESTONE`,旧版 — 推荐 `GITLAB_TOOLSETS=milestones`)
142
- - `--use-pipeline=true` - 启用流水线 API(替代 `USE_PIPELINE`,旧版 — 推荐 `GITLAB_TOOLSETS=pipelines`)
140
+ - `--use-wiki=true` - Deprecated (removed in next major (v3.0.0));请改用 `--toolsets=core,wiki`(替代 `USE_GITLAB_WIKI`)
141
+ - `--use-milestone=true` - Deprecated (removed in next major (v3.0.0));请改用 `--toolsets=core,milestones`(替代 `USE_MILESTONE`)
142
+ - `--use-pipeline=true` - Deprecated (removed in next major (v3.0.0));请改用 `--toolsets=core,pipelines`,并把 `validate_ci_lint,validate_project_ci_lint` 追加到现有 `--tools` 列表(如果还没有该列表,则设置 `--tools=validate_ci_lint,validate_project_ci_lint`)(替代 `USE_PIPELINE`)
143
143
  - `--disable-version-check=true` - 关闭启动时的新版本提示(替代 `GITLAB_DISABLE_VERSION_CHECK`)
144
144
  - `--masking-enabled=true` - 启用文本响应掩码(替代 `GITLAB_MASKING_ENABLED`)
145
145
  - `--masking-config` - 掩码配置文件路径(替代 `GITLAB_MASKING_CONFIG`)
@@ -147,6 +147,10 @@ command = lib.getExe inputs.gitlab-mcp.packages.${system}.default;
147
147
  - `--masking-workspace-dir` - 用于解析掩码文件的目录(替代 `GITLAB_MASKING_WORKSPACE_DIR`)
148
148
  - `--compact-results=true` - 将过大的 MCP 工具回复截断为预览并附带 CLI 命令(替代 `GITLAB_MCP_COMPACT_RESULTS`,默认关闭)
149
149
  - `--compact-result-chars` - compact 字符阈值(替代 `GITLAB_MCP_COMPACT_RESULT_CHARS`,默认 `4000`)
150
+ - `--compact-tools` - 以逗号分隔的工具名,仅在回复过大时对这些工具进行 compact,而不开启全局 compact(替代 `GITLAB_MCP_COMPACT_TOOLS`)
151
+ - `--tool-profile` - `full`(默认)或 `slim`。`slim` 会从初始列表中移除 draft note、表情回应、标签、CI 目录工具以及 `create_group`。仅当 `GITLAB_TOOLSETS` 设置为非空白值时忽略(替代 `GITLAB_TOOL_PROFILE`)
152
+
153
+ 如果已经设置了 `GITLAB_TOOLSETS` 或 `--toolsets`,请把 `wiki`、`milestones` 或 `pipelines` 追加到该列表,不要整段替换。如果已经设置了 `GITLAB_TOOLS` 或 `--tools`,请把 `validate_ci_lint` 和 `validate_project_ci_lint` 追加到该列表,不要整段替换。显式列表会替换默认的 `core` toolset,不会合并。
150
154
 
151
155
  CLI 参数优先于环境变量。
152
156
 
@@ -156,16 +160,21 @@ CLI 参数优先于环境变量。
156
160
 
157
161
  > **细粒度工具过滤:**使用 `GITLAB_PERMISSION_MODE=modify` 允许创建/更新,同时阻止所有删除工具以及
158
162
  > 破坏性的拆除(teardown)工具(`cancel_pipeline`、`cancel_pipeline_job`、`stop_environment`、
159
- > `stop_stale_environments`、`unprotect_branch`)(包括通过 `execute_graphql` 的破坏性 mutation
163
+ > `stop_stale_environments`、`unprotect_branch`、`unprotect_tag`)(包括通过 `execute_graphql` 的破坏性 mutation
160
164
  > —— 删除与拆除动词 —— 以及 `push_files` 的 `delete`/`move`),或使用
161
165
  > `GITLAB_PERMISSION_MODE=readonly` 只读运行。还可以用
162
166
  > `GITLAB_TOOLSETS=<group,…>` 启用工具分组,用 `GITLAB_TOOLS=<tool,…>` 白名单启用单个工具
163
167
  > (例如:只读分组 + 少数几个写工具),用 `GITLAB_DENIED_TOOLS_REGEX` 按正则屏蔽工具。
164
- > 旧版 `USE_GITLAB_WIKI` / `USE_MILESTONE` / `USE_PIPELINE` 标志仅为向后兼容保留。
168
+ > 旧版 `USE_GITLAB_WIKI` / `USE_MILESTONE` / `USE_PIPELINE` 已弃用
169
+ > (Deprecated, removed in next major (v3.0.0))。
170
+ > 请改用 `GITLAB_TOOLSETS=core,wiki`、`core,milestones` 或 `core,pipelines`
171
+ > (`USE_PIPELINE` 还需要把 `validate_ci_lint,validate_project_ci_lint` 追加到现有 `GITLAB_TOOLS`,不要整段替换)。
172
+ > 如果已经设置了 `GITLAB_TOOLSETS`,请把 toolset 追加到现有列表,不要整段替换。
173
+ > 如果未设置 `GITLAB_TOOLS`,请设置 `GITLAB_TOOLS=validate_ci_lint,validate_project_ci_lint`。如果已经设置,请把 `validate_ci_lint,validate_project_ci_lint` 追加到现有列表,不要整段替换。
165
174
  > 参见 [Tools Reference](./docs/tools/index.md#feature-toggles) 和
166
175
  > [Environment Variables](./docs/configuration/environment-variables.md)。
167
176
 
168
- #### SSE
177
+ - sse
169
178
 
170
179
  ```shell
171
180
  docker run -i --rm \
@@ -173,7 +182,7 @@ docker run -i --rm \
173
182
  -e GITLAB_PERSONAL_ACCESS_TOKEN=your_gitlab_token \
174
183
  -e GITLAB_API_URL="https://gitlab.com/api/v4" \
175
184
  -e GITLAB_PERMISSION_MODE=readonly \
176
- -e GITLAB_TOOLSETS=wiki,milestones,pipelines \
185
+ -e GITLAB_TOOLSETS=core,wiki,milestones,pipelines \
177
186
  -e SSE=true \
178
187
  -e SSE_AUTH_TOKEN=your_mcp_sse_token \
179
188
  -p 3333:3002 \
@@ -194,7 +203,7 @@ docker run -i --rm \
194
203
  }
195
204
  ```
196
205
 
197
- #### Streamable HTTP
206
+ - streamable-http
198
207
 
199
208
  ```shell
200
209
  docker run -i --rm \
@@ -202,7 +211,7 @@ docker run -i --rm \
202
211
  -e REMOTE_AUTHORIZATION=true \
203
212
  -e GITLAB_API_URL="https://gitlab.com/api/v4" \
204
213
  -e GITLAB_PERMISSION_MODE=readonly \
205
- -e GITLAB_TOOLSETS=wiki,milestones,pipelines \
214
+ -e GITLAB_TOOLSETS=core,wiki,milestones,pipelines \
206
215
  -e STREAMABLE_HTTP=true \
207
216
  -p 3333:3002 \
208
217
  zereight050/gitlab-mcp
@@ -522,7 +531,7 @@ node build/index.js
522
531
  | `MCP_SERVER_URL` | 是 | MCP 服务器的公开 HTTPS URL |
523
532
  | `GITLAB_API_URL` | 是 | GitLab 实例 API URL(例如 `https://gitlab.com/api/v4`) |
524
533
  | `STREAMABLE_HTTP` | 是 | 必须为 `true`(不支持 SSE) |
525
- | `GITLAB_OAUTH_SCOPES` | 否 | 要请求的 GitLab scopes,以逗号分隔。默认值为 `api`,当 `GITLAB_READ_ONLY_MODE=true` 时为 `read_api`。预注册应用必须配置至少这些 scopes。 |
534
+ | `GITLAB_OAUTH_SCOPES` | 否 | 要请求的 GitLab scopes,以逗号分隔。默认值为 `api`;有效 permission mode 为 `readonly` 时为 `read_api`。预注册应用必须配置至少这些 scopes。 |
526
535
  | `OAUTH_REGISTER_RATE_LIMIT_PER_HOUR` | 否 | Dynamic Client Registration(`POST /register`)的每客户端 IP rolling 限制。默认 `20`/小时,范围 `1`–`1000`。多个 IDE 窗口等导致注册被限流时可调高。与 GitLab API 限额无关。 |
527
536
  | `MCP_DANGEROUSLY_ALLOW_INSECURE_ISSUER_URL` | 否 | 仅用于本地 HTTP 开发 |
528
537
 
package/build/auth-cli.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import * as os from "os";
2
2
  import * as path from "path";
3
+ import { parseCliArgs } from "./cli-boolean-flags.js";
4
+ import { deprecatedEnvInputFromSources, getDeprecatedEnvWarnings } from "./deprecated-env.js";
3
5
  import { GitLabOAuth } from "./oauth.js";
4
- import { createLogger } from "./utils/logger.js";
6
+ import { createLogger, flushStartupLogs } from "./utils/logger.js";
5
7
  const logger = createLogger("gitlab-mcp-auth");
6
8
  const DEFAULT_API_URL = "https://gitlab.com";
7
9
  const DEFAULT_REDIRECT_URI = "http://127.0.0.1:8888/callback";
@@ -42,12 +44,12 @@ function readFlag(argv, name) {
42
44
  function wantsHelp(argv) {
43
45
  return argv.includes("--help") || argv.includes("-h");
44
46
  }
45
- function isReadOnlyMode(argv, env) {
46
- const readOnly = readFlag(argv, "read-only") ?? env.GITLAB_READ_ONLY_MODE;
47
+ function isReadOnlyMode(parsedArgs, env) {
48
+ const readOnly = parsedArgs["read-only"] ?? env.GITLAB_READ_ONLY_MODE;
47
49
  if (readOnly === "true") {
48
50
  return true;
49
51
  }
50
- return (readFlag(argv, "permission-mode") ?? env.GITLAB_PERMISSION_MODE) === "readonly";
52
+ return (parsedArgs["permission-mode"] ?? env.GITLAB_PERMISSION_MODE) === "readonly";
51
53
  }
52
54
  export function gitlabOriginFromApiUrl(apiUrl) {
53
55
  return apiUrl.replace(/\/api\/v4\/?$/, "");
@@ -56,6 +58,14 @@ export async function runAuthCommandAsync(input = {}) {
56
58
  const argv = input.argv ?? process.argv;
57
59
  const env = input.env ?? process.env;
58
60
  const stdout = input.stdout ?? process.stdout;
61
+ const parsedArgs = parseCliArgs(argv.slice(2));
62
+ const warnings = getDeprecatedEnvWarnings(deprecatedEnvInputFromSources(parsedArgs, env));
63
+ for (const warning of warnings) {
64
+ logger.warn(warning);
65
+ }
66
+ if (warnings.length > 0) {
67
+ flushStartupLogs(logger);
68
+ }
59
69
  if (wantsHelp(argv)) {
60
70
  stdout.write(AUTH_CLI_HELP);
61
71
  return;
@@ -69,7 +79,7 @@ export async function runAuthCommandAsync(input = {}) {
69
79
  env.GITLAB_OAUTH_TOKEN_PATH ??
70
80
  path.join(os.homedir(), ".gitlab-mcp-token.json");
71
81
  const gitlabUrl = gitlabOriginFromApiUrl(apiUrl);
72
- const scopes = [isReadOnlyMode(argv, env) ? "read_api" : "api"];
82
+ const scopes = [isReadOnlyMode(parsedArgs, env) ? "read_api" : "api"];
73
83
  const oauth = new GitLabOAuth({
74
84
  clientId,
75
85
  clientSecret: env.GITLAB_OAUTH_CLIENT_SECRET,
@@ -36,3 +36,37 @@ export function nextBooleanFlagValue(nextToken) {
36
36
  }
37
37
  return { value: "true", consumeNext: false };
38
38
  }
39
+ /** Same flag parse as startup config: CLI values only, positionals ignored. */
40
+ export function parseCliArgs(argv) {
41
+ const parsed = {};
42
+ for (let index = 0; index < argv.length; index++) {
43
+ const arg = argv[index];
44
+ if (!arg.startsWith("--")) {
45
+ continue;
46
+ }
47
+ const eqIndex = arg.indexOf("=");
48
+ if (eqIndex !== -1) {
49
+ const key = arg.slice(2, eqIndex);
50
+ const value = arg.slice(eqIndex + 1);
51
+ if (value) {
52
+ parsed[key] = value;
53
+ }
54
+ continue;
55
+ }
56
+ const key = arg.slice(2);
57
+ if (BOOLEAN_CLI_FLAG_NAMES.has(key)) {
58
+ const next = nextBooleanFlagValue(argv[index + 1]);
59
+ parsed[key] = next.value;
60
+ if (next.consumeNext) {
61
+ index += 1;
62
+ }
63
+ continue;
64
+ }
65
+ const next = argv[index + 1];
66
+ if (next !== undefined && !next.startsWith("--")) {
67
+ index += 1;
68
+ parsed[key] = next;
69
+ }
70
+ }
71
+ return parsed;
72
+ }
package/build/config.js CHANGED
@@ -1,35 +1,9 @@
1
- import { BOOLEAN_CLI_FLAG_NAMES, nextBooleanFlagValue } from "./cli-boolean-flags.js";
1
+ import { parseCliArgs } from "./cli-boolean-flags.js";
2
+ import { deprecatedEnvInputFromSources } from "./deprecated-env.js";
2
3
  import { parseToolProfile } from "./tools/tool-profile.js";
3
4
  import { parseCompactToolAllowlist } from "./utils/compact-tool-allowlist.js";
4
5
  // Parse CLI arguments before anything else
5
- const args = process.argv.slice(2);
6
- export const cliArgs = {};
7
- for (let i = 0; i < args.length; i++) {
8
- const arg = args[i];
9
- if (arg.startsWith("--")) {
10
- const eqIndex = arg.indexOf("=");
11
- if (eqIndex !== -1) {
12
- const key = arg.slice(2, eqIndex);
13
- const value = arg.slice(eqIndex + 1);
14
- if (value) {
15
- cliArgs[key] = value;
16
- }
17
- }
18
- else {
19
- const key = arg.slice(2);
20
- if (BOOLEAN_CLI_FLAG_NAMES.has(key)) {
21
- const parsed = nextBooleanFlagValue(args[i + 1]);
22
- cliArgs[key] = parsed.value;
23
- if (parsed.consumeNext) {
24
- i += 1;
25
- }
26
- }
27
- else if (i + 1 < args.length && !args[i + 1].startsWith("--")) {
28
- cliArgs[key] = args[++i];
29
- }
30
- }
31
- }
32
- }
6
+ export const cliArgs = parseCliArgs(process.argv.slice(2));
33
7
  export function getConfig(cliKey, envKey, defaultValue) {
34
8
  return cliArgs[cliKey] || process.env[envKey] || defaultValue;
35
9
  }
@@ -103,7 +77,7 @@ export const GITLAB_OAUTH_SCOPES = GITLAB_OAUTH_SCOPES_RAW
103
77
  .filter(Boolean)
104
78
  : undefined;
105
79
  export const GITLAB_OAUTH_CALLBACK_PROXY = getConfig("oauth-callback-proxy", "GITLAB_OAUTH_CALLBACK_PROXY") === "true";
106
- /** @deprecated Use GITLAB_OAUTH_ALLOWED_GROUPS_RAW instead. Will be removed in the next major version. */
80
+ /** @deprecated Use GITLAB_OAUTH_ALLOWED_GROUPS_RAW instead. Will be removed in v3.0.0. */
107
81
  export const GITLAB_ALLOWED_GROUPS_RAW = getConfig("allowed-groups", "GITLAB_ALLOWED_GROUPS");
108
82
  export const GITLAB_OAUTH_ALLOWED_GROUPS_RAW = getConfig("oauth-allowed-groups", "GITLAB_OAUTH_ALLOWED_GROUPS");
109
83
  export const GITLAB_OAUTH_ALLOWED_GROUPS = (() => {
@@ -202,5 +176,13 @@ export const HTTPS_PROXY = getConfig("https-proxy", "HTTPS_PROXY");
202
176
  export const NO_PROXY = getConfig("no-proxy", "NO_PROXY");
203
177
  export const NODE_TLS_REJECT_UNAUTHORIZED = getConfig("tls-reject-unauthorized", "NODE_TLS_REJECT_UNAUTHORIZED");
204
178
  export const GITLAB_CA_CERT_PATH = getConfig("ca-cert-path", "GITLAB_CA_CERT_PATH");
179
+ /**
180
+ * Snapshot of deprecated settings for startup warnings.
181
+ * CLI flags win over env vars, matching getConfig, so the warning can name
182
+ * the flag the user actually passed.
183
+ */
184
+ export function buildDeprecatedEnvInput() {
185
+ return deprecatedEnvInputFromSources(cliArgs, process.env);
186
+ }
205
187
  const _poolMaxSizeRaw = getConfig("pool-max-size", "GITLAB_POOL_MAX_SIZE");
206
188
  export const GITLAB_POOL_MAX_SIZE = _poolMaxSizeRaw ? Number.parseInt(_poolMaxSizeRaw, 10) : 100;
@@ -5,15 +5,116 @@ function shownEnvValue(rawValue) {
5
5
  }
6
6
  return `${singleLine.slice(0, 77)}...`;
7
7
  }
8
- function legacyToolsetWarning(envName, rawValue, toolset) {
8
+ const REMOVAL_VERSION = "v3.0.0";
9
+ const DEPRECATION_NOTICE_URL = "https://github.com/zereight/gitlab-mcp/issues/815";
10
+ // USE_PIPELINE exposes these two tools, but they live in the `ci` toolset, not `pipelines`.
11
+ // Suggesting `ci` would also turn on catalog tools the flag does not expose.
12
+ const LEGACY_TOOLS_OUTSIDE_NAMED_TOOLSET = {
13
+ pipelines: "validate_ci_lint,validate_project_ci_lint",
14
+ };
15
+ function explicitToolsets(raw) {
16
+ if (raw === undefined) {
17
+ return undefined;
18
+ }
19
+ const trimmed = raw.trim();
20
+ if (trimmed === "") {
21
+ return undefined;
22
+ }
23
+ return trimmed;
24
+ }
25
+ function isAllToolsets(raw) {
26
+ return raw.trim().toLowerCase() === "all";
27
+ }
28
+ function settingName(envName, cliFlag, fromCli) {
29
+ if (fromCli) {
30
+ return `\`--${cliFlag}\` (${envName})`;
31
+ }
32
+ return envName;
33
+ }
34
+ function gitlabToolsAssignment(extraTools, toolsRaw, toolsFromCli) {
35
+ const prefix = toolsFromCli ? "--tools=" : "GITLAB_TOOLS=";
36
+ const existing = explicitToolsets(toolsRaw);
37
+ if (existing === undefined) {
38
+ return `${prefix}${extraTools}`;
39
+ }
40
+ const names = existing
41
+ .split(",")
42
+ .map(part => part.trim())
43
+ .filter(part => part.length > 0);
44
+ const merged = extraTools.split(",").reduce((current, tool) => {
45
+ if (current.includes(tool)) {
46
+ return current;
47
+ }
48
+ return [...current, tool];
49
+ }, names);
50
+ return `${prefix}${merged.join(",")}`;
51
+ }
52
+ function legacyToolsetHint(toolset, toolsetsRaw, toolsetsFromCli, toolsRaw, toolsFromCli, optional) {
53
+ const extraTools = LEGACY_TOOLS_OUTSIDE_NAMED_TOOLSET[toolset];
54
+ const toolsAssignment = extraTools === undefined
55
+ ? undefined
56
+ : gitlabToolsAssignment(extraTools, toolsRaw, toolsFromCli);
57
+ const listed = explicitToolsets(toolsetsRaw);
58
+ const toolsetsLabel = toolsetsFromCli && listed !== undefined ? "--toolsets" : "GITLAB_TOOLSETS";
59
+ if (listed !== undefined) {
60
+ const shown = shownEnvValue(listed);
61
+ // parseEnabledToolsets treats only the exact value "all" as every toolset.
62
+ // "all,pipelines" is not "all"; unknown token "all" is dropped and pipelines remains.
63
+ if (isAllToolsets(listed)) {
64
+ return `Keep the existing ${toolsetsLabel} value ("${shown}") and remove the deprecated setting`;
65
+ }
66
+ const extra = toolsAssignment === undefined ? "" : ` and set ${toolsAssignment}`;
67
+ if (optional) {
68
+ return (`Add \`${toolset}\` to the existing ${toolsetsLabel} list ("${shown}")${extra} ` +
69
+ "only if you want those tools");
70
+ }
71
+ return `Add \`${toolset}\` to the existing ${toolsetsLabel} list ("${shown}")${extra} instead`;
72
+ }
73
+ // An explicit toolset list replaces the default toolsets; it does not merge.
74
+ // Legacy USE_* flags add tools on top of defaults, so the hint must keep `core`
75
+ // only when the user has not set a toolset list.
76
+ const toolsets = `GITLAB_TOOLSETS=core,${toolset}`;
77
+ const replacement = toolsAssignment === undefined ? toolsets : `${toolsets} and ${toolsAssignment}`;
78
+ if (optional) {
79
+ return `Set ${replacement} only if you want those tools in addition to core`;
80
+ }
81
+ return `Use ${replacement} instead`;
82
+ }
83
+ function legacyToolsetWarning(envName, cliFlag, fromCli, rawValue, toolset, toolsetsRaw, toolsetsFromCli, toolsRaw, toolsFromCli) {
84
+ const name = settingName(envName, cliFlag, fromCli);
9
85
  if (rawValue === "true") {
10
- return (`${envName} is deprecated and will be removed in the next major version. ` +
11
- `Use GITLAB_TOOLSETS=${toolset} instead.`);
86
+ return (`${name} is deprecated and will be removed in ${REMOVAL_VERSION}. ` +
87
+ `${legacyToolsetHint(toolset, toolsetsRaw, toolsetsFromCli, toolsRaw, toolsFromCli, false)}.`);
12
88
  }
13
89
  const shown = shownEnvValue(rawValue);
14
- return (`${envName} is set to "${shown}" and is deprecated. ` +
90
+ return (`${name} is set to "${shown}" and is deprecated and will be removed in ${REMOVAL_VERSION}. ` +
15
91
  `This value does not enable the ${toolset} toolset. Remove it. ` +
16
- `Set GITLAB_TOOLSETS=${toolset} only if you want those tools enabled.`);
92
+ `${legacyToolsetHint(toolset, toolsetsRaw, toolsetsFromCli, toolsRaw, toolsFromCli, true)}.`);
93
+ }
94
+ function readOnlyRawValue(input) {
95
+ if (input.readOnlyRaw !== undefined && input.readOnlyRaw !== "") {
96
+ return input.readOnlyRaw;
97
+ }
98
+ if (input.readOnlyMode) {
99
+ return "true";
100
+ }
101
+ return undefined;
102
+ }
103
+ function readOnlyWarning(input) {
104
+ const raw = readOnlyRawValue(input);
105
+ if (raw === undefined) {
106
+ return undefined;
107
+ }
108
+ const name = settingName("GITLAB_READ_ONLY_MODE", "read-only", input.readOnlyFromCli === true);
109
+ if (raw === "true") {
110
+ const conflict = input.permissionModeRaw !== undefined && input.permissionModeRaw !== "readonly"
111
+ ? ` It OVERRIDES the configured permission mode "${input.permissionModeRaw}"; the server runs as "readonly".`
112
+ : "";
113
+ return (`${name} is deprecated and will be removed in ${REMOVAL_VERSION}. ` +
114
+ `Use GITLAB_PERMISSION_MODE=readonly or --permission-mode=readonly instead.${conflict}`);
115
+ }
116
+ return (`${name} is set to "${shownEnvValue(raw)}" and is deprecated and will be removed in ${REMOVAL_VERSION}. ` +
117
+ "This value does not enable legacy read-only mode. The effective permission mode comes from GITLAB_PERMISSION_MODE or --permission-mode. Remove it.");
17
118
  }
18
119
  /**
19
120
  * Builds startup warnings for deprecated env vars / CLI flags.
@@ -21,27 +122,72 @@ function legacyToolsetWarning(envName, rawValue, toolset) {
21
122
  */
22
123
  export function getDeprecatedEnvWarnings(input) {
23
124
  const warnings = [];
24
- if (input.readOnlyMode) {
25
- const conflict = input.permissionModeRaw !== undefined && input.permissionModeRaw !== "readonly"
26
- ? ` It OVERRIDES the configured permission mode "${input.permissionModeRaw}"; the server runs as "readonly".`
27
- : "";
28
- warnings.push("GITLAB_READ_ONLY_MODE is deprecated and will be removed in the next major version. " +
29
- `Use GITLAB_PERMISSION_MODE=readonly or --permission-mode=readonly instead.${conflict}`);
125
+ const readOnly = readOnlyWarning(input);
126
+ if (readOnly !== undefined) {
127
+ warnings.push(readOnly);
30
128
  }
31
129
  if (input.allowedGroupsRaw) {
130
+ const name = settingName("GITLAB_ALLOWED_GROUPS", "allowed-groups", input.allowedGroupsFromCli === true);
32
131
  warnings.push(input.oauthAllowedGroupsRaw
33
- ? "GITLAB_ALLOWED_GROUPS is deprecated and ignored because GITLAB_OAUTH_ALLOWED_GROUPS is set. Remove it."
34
- : "GITLAB_ALLOWED_GROUPS is deprecated and will be removed in the next major version. Use GITLAB_OAUTH_ALLOWED_GROUPS instead.");
132
+ ? `${name} is deprecated and ignored because GITLAB_OAUTH_ALLOWED_GROUPS is set. Remove it. It will be removed in ${REMOVAL_VERSION}.`
133
+ : `${name} is deprecated and will be removed in ${REMOVAL_VERSION}. Use GITLAB_OAUTH_ALLOWED_GROUPS instead.`);
35
134
  }
36
135
  const legacyToolsetFlags = [
37
- [input.useWikiRaw, "USE_GITLAB_WIKI", "wiki"],
38
- [input.useMilestoneRaw, "USE_MILESTONE", "milestones"],
39
- [input.usePipelineRaw, "USE_PIPELINE", "pipelines"],
136
+ {
137
+ rawValue: input.useWikiRaw,
138
+ envName: "USE_GITLAB_WIKI",
139
+ cliFlag: "use-wiki",
140
+ fromCli: input.useWikiFromCli === true,
141
+ toolset: "wiki",
142
+ },
143
+ {
144
+ rawValue: input.useMilestoneRaw,
145
+ envName: "USE_MILESTONE",
146
+ cliFlag: "use-milestone",
147
+ fromCli: input.useMilestoneFromCli === true,
148
+ toolset: "milestones",
149
+ },
150
+ {
151
+ rawValue: input.usePipelineRaw,
152
+ envName: "USE_PIPELINE",
153
+ cliFlag: "use-pipeline",
154
+ fromCli: input.usePipelineFromCli === true,
155
+ toolset: "pipelines",
156
+ },
40
157
  ];
41
- for (const [rawValue, envName, toolset] of legacyToolsetFlags) {
42
- if (rawValue !== undefined) {
43
- warnings.push(legacyToolsetWarning(envName, rawValue, toolset));
158
+ for (const flag of legacyToolsetFlags) {
159
+ if (flag.rawValue !== undefined) {
160
+ warnings.push(legacyToolsetWarning(flag.envName, flag.cliFlag, flag.fromCli, flag.rawValue, flag.toolset, input.toolsetsRaw, input.toolsetsFromCli === true, input.toolsRaw, input.toolsFromCli === true));
44
161
  }
45
162
  }
46
- return warnings;
163
+ return warnings.map(warning => `${warning} See ${DEPRECATION_NOTICE_URL} for migration details.`);
164
+ }
165
+ function readSuppliedSetting(parsedArgs, env, cliKey, envKey) {
166
+ return parsedArgs[cliKey] || env[envKey];
167
+ }
168
+ /**
169
+ * Warning snapshot for one command. CLI flags in `parsedArgs` win over `env`,
170
+ * matching getConfig, and neither source is read from the process globals.
171
+ */
172
+ export function deprecatedEnvInputFromSources(parsedArgs, env) {
173
+ const readOnlyRaw = readSuppliedSetting(parsedArgs, env, "read-only", "GITLAB_READ_ONLY_MODE");
174
+ return {
175
+ readOnlyMode: readOnlyRaw === "true",
176
+ readOnlyRaw,
177
+ readOnlyFromCli: Boolean(parsedArgs["read-only"]),
178
+ permissionModeRaw: readSuppliedSetting(parsedArgs, env, "permission-mode", "GITLAB_PERMISSION_MODE"),
179
+ allowedGroupsRaw: readSuppliedSetting(parsedArgs, env, "allowed-groups", "GITLAB_ALLOWED_GROUPS"),
180
+ allowedGroupsFromCli: Boolean(parsedArgs["allowed-groups"]),
181
+ oauthAllowedGroupsRaw: readSuppliedSetting(parsedArgs, env, "oauth-allowed-groups", "GITLAB_OAUTH_ALLOWED_GROUPS"),
182
+ useWikiRaw: readSuppliedSetting(parsedArgs, env, "use-wiki", "USE_GITLAB_WIKI"),
183
+ useWikiFromCli: Boolean(parsedArgs["use-wiki"]),
184
+ useMilestoneRaw: readSuppliedSetting(parsedArgs, env, "use-milestone", "USE_MILESTONE"),
185
+ useMilestoneFromCli: Boolean(parsedArgs["use-milestone"]),
186
+ usePipelineRaw: readSuppliedSetting(parsedArgs, env, "use-pipeline", "USE_PIPELINE"),
187
+ usePipelineFromCli: Boolean(parsedArgs["use-pipeline"]),
188
+ toolsetsRaw: readSuppliedSetting(parsedArgs, env, "toolsets", "GITLAB_TOOLSETS"),
189
+ toolsetsFromCli: Boolean(parsedArgs["toolsets"]),
190
+ toolsRaw: readSuppliedSetting(parsedArgs, env, "tools", "GITLAB_TOOLS"),
191
+ toolsFromCli: Boolean(parsedArgs["tools"]),
192
+ };
47
193
  }