npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gen-bruno

v1.6.4

Published

Convert OpenAPI, Swagger, GraphQL, and gRPC specs into Bruno API collections

Downloads

1,393

Readme

gen-bruno

Convert OpenAPI 3.x, Swagger 2.0, GraphQL schemas, gRPC Protocol Buffer specs, and AsyncAPI WebSocket specs into fully functional Bruno API collections with one command. Available as a CLI tool and a programmatic JavaScript/TypeScript library.

gen-bruno ./openapi.yaml ./output

Features

  • OpenAPI 3.x — Full support for paths, parameters, request bodies, responses, and examples
  • Swagger 2.0 — Legacy Swagger specs automatically normalized and converted
  • GraphQL — SDL schemas converted to executable Bruno GraphQL requests, grouped into query/mutation folders
  • gRPC.proto service definitions converted to native Bruno gRPC requests, including streaming modes
  • AsyncAPI WebSockets — AsyncAPI 2.x and 3.x ws/wss servers converted to native Bruno WebSocket requests and message bodies
  • Authentication — Bearer, Basic, API Key, OAuth2, and OpenID Connect security schemes
  • Examples — Preserves spec examples and auto-generates placeholder values from schemas
  • Folder grouping — Organize by tags, URL paths, or flat structure
  • Plugins — Transform the IR or modify .bru output before writing
  • Config filesbrunogen.config.yml for reproducible, team-shared settings
  • Dual ESM/CJS — Works in both import and require projects

Quick Start

# Install globally
npm install -g gen-bruno

# Generate from a spec file
gen-bruno ./openapi.yaml ./output

# Or use npx without installing
npx gen-bruno ./openapi.yaml ./output

# Open the output folder in Bruno — done

Installation

# Global CLI
npm install -g gen-bruno

# Or as a project dependency
npm install gen-bruno

Requires Node.js 24+.

CLI Usage

gen-bruno [spec] [output] [options]

Options

| Flag | Description | Default | |------|-------------|---------| | <spec> | Path to an OpenAPI, Swagger, GraphQL, gRPC .proto, or AsyncAPI WebSocket spec file | (required) | | [output] | Output directory for the Bruno collection | ./bruno-output | | --format <tag\|path\|flat> | Folder grouping strategy | tag | | --tests | Generate post-response test assertions | off | | --dry-run | Print tree to stdout without writing files | off | | --config <path> | Path to config file | auto-discover | | --verbose | Include stack traces in error output | off |

Examples

# OpenAPI spec → Bruno collection (grouped by tag)
gen-bruno ./openapi.yaml ./output

# GraphQL schema with test assertions
gen-bruno ./schema.graphql ./output --tests

# Protocol Buffer service definition → native Bruno gRPC requests
gen-bruno ./greeter.proto ./output

# AsyncAPI WebSocket specification → native Bruno WebSocket requests
gen-bruno ./chat.asyncapi.yaml ./output

# Dry run to preview parsed spec
gen-bruno ./openapi.yaml ./output --dry-run

# Group requests by URL path instead of tags
gen-bruno ./openapi.yaml ./output --format path

# Use a config file with overrides
gen-bruno --config ./brunogen.config.yaml --tests --verbose

Folder Grouping Strategies

  • tag (default) — Groups requests by their OpenAPI tags into Bruno folders
  • path — Groups by URL path structure (/users, /users/{id}, etc.)
  • flat — All requests in a single folder, no grouping

Config File

Place brunogen.config.yml (or .json) in your project root. The CLI auto-discovers it.

# brunogen.config.yml
spec: ./openapi.yaml        # Default spec path (CLI <spec> overrides)
output: ./bruno-output      # Default output directory
format: tag                 # Folder grouping: tag | path | flat
tests: false                # Generate test assertions
force: false                # Overwrite existing output directory
# plugins:                  # Plugin file paths (optional)
#   - ./plugins/add-headers.js

Merge priority (lowest → highest): Built-in defaults < config file < CLI flags.

Library API

Use programmatically in your own scripts. Supports both ESM and CommonJS.

Basic Usage

import { generate, parse } from "gen-bruno";

const ir = await parse("./openapi.yaml");

const result = await generate(ir, {
  outputDir: "./output",
  grouping: "tag",       // "tag" | "path" | "flat"
  generateTests: false,  // Include post-response test assertions
  force: false,          // Overwrite existing output
});

console.log(`Generated ${result.filesWritten.length} files`);

parse(input)

Unified spec parser. Accepts file paths to OpenAPI 3.x YAML/JSON, Swagger 2.0, GraphQL SDL, gRPC Protocol Buffer (.proto), or AsyncAPI 2.x/3.x WebSocket YAML/JSON files.

import { parse } from "gen-bruno";

// OpenAPI 3.x
const ir = await parse("./openapi.yaml");

// Swagger 2.0
const ir = await parse("./swagger.json");

// GraphQL SDL
const ir = await parse("./schema.graphql");

// gRPC Protocol Buffer service definition
const ir = await parse("./greeter.proto");

// AsyncAPI WebSocket document
const ir = await parse("./chat.asyncapi.yaml");

// Explicitly select a format when inline content is ambiguous
const inline = await parse({
  content: 'openapi: "3.0.0"\ninfo: { title: API, version: "1" }\npaths: {}',
  format: "openapi",
});

GraphQL output

Each root Query and Mutation field becomes a POST /graphql request. Generated requests use Bruno's native body:graphql and body:graphql:vars blocks, with an operation document, typed variables, and a schema-derived response selection. The collection defaults baseUrl to http://localhost:4000; update it in Bruno to match your API server.

Returns a CollectionIR object with normalized endpoints, parameters, security schemes, and response schemas.

gRPC output

Each service RPC in a .proto file becomes a native Bruno gRPC request. The generated collection includes the input proto at protos/<filename> and each request references it with a relative protoPath, making the collection portable. The default baseUrl is http://localhost:50051; update it in Bruno to match your gRPC server. Unary, client-streaming, server-streaming, and bidirectional-streaming RPCs map to Bruno's corresponding request modes.

AsyncAPI WebSocket output

AsyncAPI 2.x and 3.x documents with ws or wss servers are supported. Each publish/send and subscribe/receive operation becomes a native Bruno WebSocket request. Send operations include JSON, text, or XML message examples; receive operations create a connection request so the documented server messages can be observed. The collection's baseUrl is taken from the first WebSocket server.

CollectionBuilder

Fluent builder API for chaining configuration and generation.

import { CollectionBuilder } from "gen-bruno";

// From a spec file
const result = await CollectionBuilder.fromSpec("./openapi.yaml")
  .withOptions({ grouping: "path", generateTests: true })
  .withPlugins([myHeaderPlugin])
  .generate("./output");

// Reuse the same builder for different outputs
const builder = CollectionBuilder.fromSpec("./openapi.yaml");

await builder.withOptions({ grouping: "tag" }).generate("./out-tag");
await builder.withOptions({ grouping: "flat" }).generate("./out-flat");

// Or keep the output directory in the immutable builder options
await CollectionBuilder.fromSpec("./openapi.yaml")
  .withOptions({ outputDir: "./output", grouping: "tag" })
  .generate();

loadConfig(cwd?, configPath?)

Load and merge a config file from the filesystem.

import { loadConfig } from "gen-bruno";

// Auto-discover config in CWD
const config = await loadConfig();

// Load specific config file
const config = await loadConfig(process.cwd(), "./config/brunogen.yaml");

mergeConfig(defaults, configFile, cliFlags)

Three-layer config merge utility.

import { mergeConfig } from "gen-bruno";

const merged = mergeConfig(
  { format: "tag", tests: false },   // Built-in defaults
  { spec: "./api.yaml", tests: true }, // Config file
  { format: "path" },                // CLI flags
);

// Result: { spec: "./api.yaml", format: "path", tests: true }

Plugins

Plugins let you transform the IR before generation or modify .bru content before writing.

Plugin Interface

interface Plugin {
  name: string;
  hooks: {
    // Transform the CollectionIR after parsing, before generation
    transformIR?: (ir: CollectionIR, ctx: PluginContext) => Promise<CollectionIR>;
    // Transform file content right before writing
    preOutput?: (content: string, ctx: PreOutputContext) => Promise<string>;
  };
}

Example: Add a Custom Header

const addHeaderPlugin: Plugin = {
  name: "add-custom-header",
  hooks: {
    preOutput: async (content) => {
      return content.includes("method:")
        ? content + "\nheader: X-Custom-Header: my-value\n"
        : content;
    },
  },
};

Example: Auth Header Injection

import type { Plugin } from "gen-bruno";

const authPlugin: Plugin = {
  name: "auth-header-injector",
  hooks: {
    transformIR: async (ir) => ({
      ...ir,
      endpoints: ir.endpoints.map((ep) => ({
        ...ep,
        headers: [
          ...(ep.headers || []),
          { name: "Authorization", value: "{{apiKey}}", required: true },
        ],
      })),
    }),
  },
};

// Use via CollectionBuilder
await CollectionBuilder.fromSpec("./openapi.yaml")
  .withPlugins([authPlugin])
  .generate("./output");

Using Plugins

Via config file:

plugins:
  - ./plugins/add-headers.js
  - ./plugins/auth-injector.js

Via builder API:

await CollectionBuilder.fromSpec("./api.yaml")
  .withPlugins([plugin1, plugin2])
  .generate("./output");

Via generate() function:

await generate(ir, {
  outputDir: "./output",
  plugins: [myPlugin],
});

Output Structure

The generator produces a complete Bruno collection directory:

output/
├── bruno.json                    # Bruno collection metadata
├── collection.bru                # Collection-level config (meta, auth, vars, docs)
├── environments/
│   └── default.bru               # Environment variables (auth tokens, etc.)
├── Weather/                      # Folder per tag (or path segment)
│   └── getweatherdata.bru        # One .bru file per endpoint
└── Users/
    ├── createuser.bru
    ├── getuser.bru
    └── updateuser.bru

Each .bru file contains the full request definition — method, URL, parameters, headers, body, auth, tests, and docs — ready to use in Bruno.

Troubleshooting

No output generated

  • Verify the spec file path is correct and accessible
  • Check that the spec is valid OpenAPI 3.x, Swagger 2.0, or GraphQL SDL
  • Run with --verbose to see detailed error output including stack traces

Invalid Bruno output

  • Validate your spec first: gen-bruno ./openapi.yaml ./output --dry-run
  • Check for unsupported features in the warnings output
  • Ensure $ref references in your spec resolve correctly

Module not found

  • Ensure you're running Node.js 24 or higher (node --version)
  • For ESM projects: use import { generate } from "gen-bruno"
  • For CommonJS projects: use const { generate } = require("gen-bruno")

Plugin not loaded

  • Verify the plugin file path is correct (relative to CWD or absolute)
  • Ensure the plugin exports an object with name and hooks properties
  • Check that each hook is an async function returning the transformed value

Config file not discovered

  • Place brunogen.config.yml or brunogen.config.json in your project root
  • Or specify explicitly: gen-bruno --config ./path/to/config.yaml

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/my-feature)
  3. Make your changes
  4. Run tests: npm test
  5. Ensure build passes: npm run build
  6. Check linting: npm run lint && npm run format:check
  7. Open a Pull Request

All contributions welcome — bug fixes, new features, docs improvements, and real-world spec validation.