All files / src/cli/init config-generator.ts

100% Statements 32/32
90.9% Branches 10/11
100% Functions 7/7
100% Lines 32/32

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141        2x             18x                     2x   26x   26x           26x 26x 26x       26x 1x     26x                   2x 7x   7x           7x           2x 6x     6x 18x       6x           2x           4x     4x 1x                 3x 2x               1x                         2x 3x   3x           3x           3x    
/**
 * Configuration generator for various MCP clients
 */
 
import { McpServerConfig, WizardConfig, MCP_CLIENT_INFO, ROLE_PRESETS } from "./types";
 
/**
 * Escape a string for safe use in shell double-quoted context.
 * Escapes: $ ` " \ and newlines
 */
function shellEscape(value: string): string {
  return value
    .replace(/\\/g, "\\\\")
    .replace(/"/g, '\\"')
    .replace(/\$/g, "\\$")
    .replace(/`/g, "\\`")
    .replace(/\n/g, "\\n");
}
 
/**
 * Generate MCP server configuration object
 */
export function generateServerConfig(config: WizardConfig): McpServerConfig {
  // Normalize instance URL to avoid double-appending /api/v4 later
  const normalizedUrl = config.instanceUrl.replace(/\/+$/, "").replace(/\/api\/v4$/i, "");
 
  const env: Record<string, string> = {
    GITLAB_API_URL: normalizedUrl,
    GITLAB_TOKEN: config.token,
  };
 
  // Add preset if mapped from role
  const presetName = ROLE_PRESETS[config.role];
  Eif (presetName) {
    env.GITLAB_MCP_PRESET = presetName;
  }
 
  // Add read-only mode if selected
  if (config.readOnly) {
    env.GITLAB_READ_ONLY_MODE = "true";
  }
 
  return {
    command: "npx",
    args: ["-y", "@structured-world/gitlab-mcp@latest"],
    env,
  };
}
 
/**
 * Generate JSON configuration for mcpServers format (most clients)
 */
export function generateMcpServersJson(config: WizardConfig, serverName = "gitlab"): string {
  const serverConfig = generateServerConfig(config);
 
  const mcpServers = {
    mcpServers: {
      [serverName]: serverConfig,
    },
  };
 
  return JSON.stringify(mcpServers, null, 2);
}
 
/**
 * Generate Claude Code CLI command
 */
export function generateClaudeCodeCommand(config: WizardConfig, serverName = "gitlab"): string {
  const serverConfig = generateServerConfig(config);
 
  // Build environment variable flags with proper shell escaping
  const envFlags = Object.entries(serverConfig.env)
    .map(([key, value]) => `--env ${key}="${shellEscape(value)}"`)
    .join(" ");
 
  // claude mcp add <name> <command> [args...] --env KEY=VALUE
  return `claude mcp add ${serverName} ${serverConfig.command} ${serverConfig.args.join(" ")} ${envFlags}`;
}
 
/**
 * Generate configuration based on client type
 */
export function generateClientConfig(config: WizardConfig): {
  type: "json" | "cli" | "instructions";
  content: string;
  configPath?: string;
  cliCommand?: string;
} {
  const clientInfo = MCP_CLIENT_INFO[config.client];
 
  // Claude Code supports CLI installation
  if (config.client === "claude-code") {
    return {
      type: "cli",
      content: generateMcpServersJson(config),
      cliCommand: generateClaudeCodeCommand(config),
      configPath: clientInfo.configPath,
    };
  }
 
  // Most clients use mcpServers JSON format
  if (config.client !== "generic") {
    return {
      type: "json",
      content: generateMcpServersJson(config),
      configPath: clientInfo.configPath,
    };
  }
 
  // Generic: just provide the server config
  return {
    type: "instructions",
    content: generateMcpServersJson(config),
  };
}
 
/**
 * Generate Claude Deep Link for one-click installation
 * Format: claude://settings/mcp/add?config=BASE64
 *
 * Uses URL-safe Base64 encoding to handle characters that may be
 * problematic in query strings (+, /, =)
 */
export function generateClaudeDeepLink(config: WizardConfig, serverName = "gitlab"): string {
  const serverConfig = generateServerConfig(config);
 
  const configObject = {
    name: serverName,
    ...serverConfig,
  };
 
  // URL-safe Base64: replace + with -, / with _, and remove = padding
  const base64Config = Buffer.from(JSON.stringify(configObject))
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
 
  return `claude://settings/mcp/add?config=${base64Config}`;
}