Skip to main content
Glama

Bucket Feature Flags MCP Server

Official
by reflagcom
config.ts5.63 kB
import { Ajv, ValidateFunction } from "ajv"; import { assign as assignJSON, parse as parseJSON, stringify as stringifyJSON, } from "comment-json"; import equal from "fast-deep-equal"; import { findUp } from "find-up"; import { readFile, writeFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { CONFIG_FILE_NAME, DEFAULT_API_URL, DEFAULT_BASE_URL, DEFAULT_TYPES_OUTPUT, MODULE_ROOT, SCHEMA_URL, } from "../utils/constants.js"; import { ConfigValidationError, handleError } from "../utils/errors.js"; import { stripTrailingSlash } from "../utils/urls.js"; import { current as currentVersion } from "../utils/version.js"; export const typeFormats = ["react", "node"] as const; export type TypeFormat = (typeof typeFormats)[number]; export type TypesOutput = { path: string; format: TypeFormat; }; type Config = { $schema: string; baseUrl: string; apiUrl: string; appId: string | undefined; typesOutput: TypesOutput[]; }; const defaultConfig: Config = { $schema: SCHEMA_URL, baseUrl: DEFAULT_BASE_URL, apiUrl: DEFAULT_API_URL, appId: undefined, typesOutput: [{ path: DEFAULT_TYPES_OUTPUT, format: "react" }], }; // Helper to normalize typesOutput to array format export function normalizeTypesOutput( output?: string | TypesOutput[], ): TypesOutput[] | undefined { if (!output) return undefined; if (typeof output === "string") { return [{ path: output, format: "react" }]; } return output; } class ConfigStore { protected config: Config = { ...defaultConfig }; protected configPath: string | undefined; protected projectPath: string | undefined; protected clientVersion: string | undefined; protected validateConfig: ValidateFunction | undefined; async initialize() { await this.createValidator(); await this.loadConfigFile(); } protected async createValidator() { try { // Using current config store file, resolve the schema.json path const schemaPath = join(MODULE_ROOT, "schema.json"); const content = await readFile(schemaPath, "utf-8"); const parsed = parseJSON(content) as unknown as Config; const ajv = new Ajv(); this.validateConfig = ajv.compile(parsed); } catch { handleError(new Error("Failed to load the config schema"), "Config"); } } protected async loadConfigFile() { if (!this.validateConfig) { handleError(new Error("Failed to load the config schema"), "Config"); } // Load the client version from the module's package.json metadata try { const { version } = await currentVersion(); this.clientVersion = version; } catch { // Should not be the case, but ignore if no package.json is found } try { const projectMetadataPath = await findUp("package.json"); this.configPath = await findUp(CONFIG_FILE_NAME); this.projectPath = dirname( this.configPath ?? projectMetadataPath ?? process.cwd(), ); if (!this.configPath) return; const content = await readFile(this.configPath, "utf-8"); const parsed = parseJSON(content) as unknown as Partial<Config>; // Normalize values if (parsed.baseUrl) parsed.baseUrl = stripTrailingSlash(parsed.baseUrl.trim()); if (parsed.apiUrl) parsed.apiUrl = stripTrailingSlash(parsed.apiUrl.trim()); if (parsed.typesOutput?.length) parsed.typesOutput = normalizeTypesOutput(parsed.typesOutput); if (!this.validateConfig!(parsed)) { handleError( new ConfigValidationError(this.validateConfig!.errors), "Config", ); } this.config = assignJSON(this.config, parsed); } catch { // No config file found } } /** * Create a new config file with initial values. * @param overwrite If true, overwrites existing config file. Defaults to false */ async saveConfigFile(overwrite = false) { const configWithoutDefaults: Partial<Config> = assignJSON({}, this.config); // Only include non-default values and $schema for (const untypedKey in configWithoutDefaults) { const key = untypedKey as keyof Config; if ( !["$schema"].includes(key) && equal(configWithoutDefaults[key], defaultConfig[key]) ) { delete configWithoutDefaults[key]; } } const configJSON = stringifyJSON(configWithoutDefaults, null, 2); if (this.configPath && !overwrite) { throw new Error("Config file already exists"); } if (this.configPath) { await writeFile(this.configPath, configJSON); } else { // Write to the project path const packageJSONPath = await findUp("package.json"); this.projectPath = dirname(packageJSONPath ?? process.cwd()); this.configPath = join(this.projectPath, CONFIG_FILE_NAME); await writeFile(this.configPath, configJSON); } } getConfig(): Config; getConfig<K extends keyof Config>(key: K): Config[K]; getConfig<K extends keyof Config>(key?: K) { return key ? this.config?.[key] : this.config; } getConfigPath() { return this.configPath; } getClientVersion() { return this.clientVersion; } getProjectPath() { return this.projectPath ?? process.cwd(); } setConfig(newConfig: Partial<Config>) { // Update the config with new values skipping undefined values for (const untypedKey in newConfig) { const key = untypedKey as keyof Config; if (newConfig[key] === undefined) continue; (this.config as any)[key] = newConfig[key]; } } } export const configStore = new ConfigStore();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/reflagcom/bucket-javascript-sdk'

If you have feedback or need assistance with the MCP directory API, please join our Discord server