Skip to main content
Glama

Bucket Feature Flags MCP Server

Official
by reflagcom
flags.ts5.96 kB
import { input } from "@inquirer/prompts"; import chalk from "chalk"; import { Command } from "commander"; import { relative } from "node:path"; import ora, { Ora } from "ora"; import { App, getApp, getOrg } from "../services/bootstrap.js"; import { createFlag, Flag, listFlags } from "../services/flags.js"; import { configStore } from "../stores/config.js"; import { handleError, MissingAppIdError, MissingEnvIdError, } from "../utils/errors.js"; import { genFlagKey, genTypes, indentLines, KeyFormatPatterns, writeTypesToFile, } from "../utils/gen.js"; import { appIdOption, flagKeyOption, flagNameArgument, typesFormatOption, typesOutOption, } from "../utils/options.js"; import { baseUrlSuffix, featureUrl } from "../utils/urls.js"; type CreateFlagOptions = { key?: string; }; export const createFlagAction = async ( name: string | undefined, { key }: CreateFlagOptions, ) => { const { baseUrl, appId } = configStore.getConfig(); let spinner: Ora | undefined; if (!appId) { handleError(new MissingAppIdError(), "Flags Create"); } let app: App; try { app = getApp(appId); } catch (error) { handleError(error, "Flags Create"); } const production = app.environments.find((e) => e.isProduction); try { const org = getOrg(); console.log( `Creating flag for app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}.`, ); if (!name) { name = await input({ message: "New flag name:", validate: (text) => text.length > 0 || "Name is required.", }); } if (!key) { const keyFormat = org.featureKeyFormat; const keyValidator = KeyFormatPatterns[keyFormat]; key = await input({ message: "New flag key:", default: genFlagKey(name, keyFormat), validate: (str) => keyValidator.regex.test(str) || keyValidator.message, }); } spinner = ora(`Creating flag...`).start(); const flag = await createFlag(appId, { name, key }); spinner.succeed( `Created flag ${chalk.cyan(flag.name)} with key ${chalk.cyan(flag.key)}:`, ); if (production) { console.log( indentLines(chalk.magenta(featureUrl(baseUrl, production, flag))), ); } } catch (error) { spinner?.fail("Flag creation failed."); handleError(error, "Flags Create"); } }; export const listFlagsAction = async () => { const { baseUrl, appId } = configStore.getConfig(); let spinner: Ora | undefined; if (!appId) { handleError(new MissingAppIdError(), "Flags Create"); } try { const app = getApp(appId); const production = app.environments.find((e) => e.isProduction); if (!production) { handleError(new MissingEnvIdError(), "Flags Types"); } spinner = ora( `Loading flags of app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}...`, ).start(); const flagsResponse = await listFlags(appId, { envId: production.id, }); spinner.succeed( `Loaded flags of app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}.`, ); console.table( flagsResponse.data.map(({ key, name, stage }) => ({ name, key, stage: stage?.name, })), ); } catch (error) { spinner?.fail("Loading flags failed."); handleError(error, "Flags List"); } }; export const generateTypesAction = async () => { const { baseUrl, appId } = configStore.getConfig(); const typesOutput = configStore.getConfig("typesOutput"); let spinner: Ora | undefined; let flags: Flag[] = []; if (!appId) { handleError(new MissingAppIdError(), "Flags Types"); } let app: App; try { app = getApp(appId); } catch (error) { handleError(error, "Flags Types"); } const production = app.environments.find((e) => e.isProduction); if (!production) { handleError(new MissingEnvIdError(), "Flags Types"); } try { spinner = ora( `Loading flags of app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}...`, ).start(); flags = await listFlags(appId, { envId: production.id, includeRemoteConfigs: true, }).then((res) => res.data); spinner.succeed( `Loaded flags of app ${chalk.cyan(app.name)}${baseUrlSuffix(baseUrl)}.`, ); } catch (error) { spinner?.fail("Loading flags failed."); handleError(error, "Flags Types"); } try { spinner = ora(`Generating flag types...`).start(); const projectPath = configStore.getProjectPath(); // Generate types for each output configuration for (const output of typesOutput) { const types = genTypes(flags, output.format); const outPath = await writeTypesToFile(types, output.path, projectPath); spinner.succeed( `Generated ${output.format} types in ${chalk.cyan(relative(projectPath, outPath))}.`, ); } } catch (error) { spinner?.fail("Type generation failed."); handleError(error, "Flags Types"); } }; export function registerFlagCommands(cli: Command) { const flagsCommand = new Command("flags").description("Manage flags."); flagsCommand .command("create") .description("Create a new flag.") .addOption(appIdOption) .addOption(flagKeyOption) .addArgument(flagNameArgument) .action(createFlagAction); flagsCommand .command("list") .alias("ls") .description("List all flags.") .addOption(appIdOption) .action(listFlagsAction); flagsCommand .command("types") .description("Generate flag types.") .addOption(appIdOption) .addOption(typesOutOption) .addOption(typesFormatOption) .action(generateTypesAction); // Update the config with the cli override values flagsCommand.hook("preAction", (_, command) => { const { appId, out, format } = command.opts(); configStore.setConfig({ appId, typesOutput: out ? [{ path: out, format: format || "react" }] : undefined, }); }); cli.addCommand(flagsCommand); }

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