bruno-collection-generator

安装量: 93
排名: #8688

安装

npx skills add https://github.com/patricio0312rev/skills --skill bruno-collection-generator

Bruno Collection Generator

Generate Bruno collection files for the open-source, Git-friendly API client.

Core Workflow Scan routes: Find all API route definitions Extract metadata: Methods, paths, params, bodies Create collection: Initialize bruno.json manifest Generate .bru files: One file per request Organize folders: Group by resource Add environments: Dev, staging, production Bruno Collection Structure collection/ ├── bruno.json # Collection manifest ├── environments/ │ ├── Development.bru │ ├── Staging.bru │ └── Production.bru ├── users/ │ ├── folder.bru │ ├── get-users.bru │ ├── get-user.bru │ ├── create-user.bru │ ├── update-user.bru │ └── delete-user.bru ├── auth/ │ ├── folder.bru │ ├── login.bru │ ├── register.bru │ └── logout.bru └── products/ ├── folder.bru └── ...

bruno.json Manifest { "version": "1", "name": "My API", "type": "collection", "ignore": ["node_modules", ".git"] }

.bru File Syntax meta { name: Get Users type: http seq: 1 }

get { url: {{baseUrl}}/users body: none auth: bearer }

auth:bearer { token: {{authToken}} }

query { page: 1 limit: 10 }

headers { Accept: application/json }

docs { Retrieve a paginated list of users. }

Generator Script // scripts/generate-bruno.ts import * as fs from "fs"; import * as path from "path";

interface RouteInfo { method: string; path: string; name: string; description?: string; body?: object; queryParams?: { name: string; value: string }[]; auth?: boolean; }

interface BrunoOptions { collectionName: string; outputDir: string; baseUrl: string; authType?: "bearer" | "basic" | "apikey"; }

function generateBrunoCollection( routes: RouteInfo[], options: BrunoOptions ): void { const { outputDir, collectionName } = options;

// Create output directory if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); }

// Create bruno.json const manifest = { version: "1", name: collectionName, type: "collection", ignore: ["node_modules", ".git"], }; fs.writeFileSync( path.join(outputDir, "bruno.json"), JSON.stringify(manifest, null, 2) );

// Create environments generateEnvironments(outputDir, options);

// Group routes by resource const groupedRoutes = groupRoutesByResource(routes);

for (const [resource, resourceRoutes] of Object.entries(groupedRoutes)) { const folderPath = path.join(outputDir, resource);

if (!fs.existsSync(folderPath)) {
  fs.mkdirSync(folderPath, { recursive: true });
}

// Create folder.bru
const folderBru = `meta {\n  name: ${capitalize(resource)}\n}\n`;
fs.writeFileSync(path.join(folderPath, "folder.bru"), folderBru);

// Create request files
let seq = 1;
for (const route of resourceRoutes) {
  const fileName = generateFileName(route);
  const content = generateBruFile(route, seq++, options);
  fs.writeFileSync(path.join(folderPath, `${fileName}.bru`), content);
}

} }

function generateBruFile( route: RouteInfo, seq: number, options: BrunoOptions ): string { const lines: string[] = [];

// Meta section lines.push("meta {"); lines.push(name: ${route.name}); lines.push(" type: http"); lines.push(seq: ${seq}); lines.push("}"); lines.push("");

// Request section const method = route.method.toLowerCase(); const urlPath = route.path.replace(/:(\w+)/g, "{{$1}}");

lines.push(${method} {); lines.push(url: {{baseUrl}}${urlPath});

if (["post", "put", "patch"].includes(method) && route.body) { lines.push(" body: json"); } else { lines.push(" body: none"); }

if (route.auth && options.authType) { lines.push(auth: ${options.authType}); } else { lines.push(" auth: none"); }

lines.push("}"); lines.push("");

// Auth section if (route.auth && options.authType === "bearer") { lines.push("auth:bearer {"); lines.push(" token: {{authToken}}"); lines.push("}"); lines.push(""); } else if (route.auth && options.authType === "basic") { lines.push("auth:basic {"); lines.push(" username: {{username}}"); lines.push(" password: {{password}}"); lines.push("}"); lines.push(""); }

// Query params if (route.queryParams?.length) { lines.push("query {"); for (const param of route.queryParams) { lines.push(${param.name}: ${param.value}); } lines.push("}"); lines.push(""); }

// Headers lines.push("headers {"); lines.push(" Accept: application/json"); if (["post", "put", "patch"].includes(method)) { lines.push(" Content-Type: application/json"); } lines.push("}"); lines.push("");

// Body if (["post", "put", "patch"].includes(method) && route.body) { lines.push("body:json {"); lines.push(JSON.stringify(route.body, null, 2)); lines.push("}"); lines.push(""); }

// Docs if (route.description) { lines.push("docs {"); lines.push(${route.description}); lines.push("}"); }

return lines.join("\n"); }

function generateEnvironments(outputDir: string, options: BrunoOptions): void { const envsDir = path.join(outputDir, "environments");

if (!fs.existsSync(envsDir)) { fs.mkdirSync(envsDir, { recursive: true }); }

const environments = [ { name: "Development", baseUrl: "http://localhost:3000/api" }, { name: "Staging", baseUrl: "https://staging-api.example.com" }, { name: "Production", baseUrl: "https://api.example.com" }, ];

for (const env of environments) { const content = `vars { baseUrl: ${env.baseUrl} authToken: }

vars:secret [ authToken ] ; fs.writeFileSync(path.join(envsDir,${env.name}.bru`), content); } }

function generateFileName(route: RouteInfo): string { return route.name.toLowerCase().replace(/\s+/g, "-"); }

function groupRoutesByResource( routes: RouteInfo[] ): Record { const groups: Record = {};

for (const route of routes) { const parts = route.path.split("/").filter(Boolean); const resource = parts[0] || "api";

if (!groups[resource]) {
  groups[resource] = [];
}
groups[resource].push(route);

}

return groups; }

function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); }

Complete Example Files bruno.json { "version": "1", "name": "My API", "type": "collection", "ignore": ["node_modules", ".git"] }

environments/Development.bru vars { baseUrl: http://localhost:3000/api authToken: userId: 1 }

vars:secret [ authToken ]

environments/Production.bru vars { baseUrl: https://api.example.com authToken: userId: }

vars:secret [ authToken ]

users/folder.bru meta { name: Users }

users/get-users.bru meta { name: Get Users type: http seq: 1 }

get { url: {{baseUrl}}/users body: none auth: bearer }

auth:bearer { token: {{authToken}} }

query { page: 1 limit: 10 }

headers { Accept: application/json }

docs { Retrieve a paginated list of users.

## Query Parameters - page: Page number (default: 1) - limit: Items per page (default: 10, max: 100)

## Response Returns paginated user list with metadata. }

users/get-user.bru meta { name: Get User by ID type: http seq: 2 }

get { url: {{baseUrl}}/users/{{userId}} body: none auth: bearer }

auth:bearer { token: {{authToken}} }

headers { Accept: application/json }

docs { Retrieve a single user by their ID. }

users/create-user.bru meta { name: Create User type: http seq: 3 }

post { url: {{baseUrl}}/users body: json auth: bearer }

auth:bearer { token: {{authToken}} }

headers { Accept: application/json Content-Type: application/json }

body:json { { "name": "John Doe", "email": "john@example.com", "role": "user" } }

docs { Create a new user account.

## Request Body - name: User's full name (required) - email: User's email address (required, unique) - role: User role (optional, default: "user") }

users/update-user.bru meta { name: Update User type: http seq: 4 }

put { url: {{baseUrl}}/users/{{userId}} body: json auth: bearer }

auth:bearer { token: {{authToken}} }

headers { Accept: application/json Content-Type: application/json }

body:json { { "name": "John Updated", "email": "john.updated@example.com" } }

docs { Update an existing user. }

users/delete-user.bru meta { name: Delete User type: http seq: 5 }

delete { url: {{baseUrl}}/users/{{userId}} body: none auth: bearer }

auth:bearer { token: {{authToken}} }

headers { Accept: application/json }

docs { Delete a user account. }

auth/login.bru meta { name: Login type: http seq: 1 }

post { url: {{baseUrl}}/auth/login body: json auth: none }

headers { Accept: application/json Content-Type: application/json }

body:json { { "email": "user@example.com", "password": "password123" } }

script:post-response { if (res.body.token) { bru.setEnvVar("authToken", res.body.token); } }

docs { Authenticate user and receive access token.

On successful login, the token is automatically saved to the authToken environment variable. }

Pre/Post Request Scripts script:pre-request { // Set dynamic values before request const timestamp = Date.now(); bru.setVar("requestId", req-${timestamp}); }

script:post-response { // Extract values from response if (res.body.token) { bru.setEnvVar("authToken", res.body.token); }

if (res.body.id) { bru.setEnvVar("userId", res.body.id); }

// Log response info console.log(Status: ${res.status}); console.log(Response time: ${res.responseTime}ms); }

Tests in Bruno tests { test("should return 200", function() { expect(res.status).to.equal(200); });

test("should return array of users", function() { expect(res.body.data).to.be.an("array"); });

test("should include pagination", function() { expect(res.body.meta).to.have.property("page"); expect(res.body.meta).to.have.property("total"); }); }

CLI Script

!/usr/bin/env node

// scripts/bruno-gen.ts import * as fs from "fs"; import { program } from "commander";

program .name("bruno-gen") .description("Generate Bruno collection from API routes") .option("-f, --framework ", "Framework type", "express") .option("-s, --source ", "Source directory", "./src") .option("-o, --output ", "Output directory", "./bruno-collection") .option("-n, --name ", "Collection name", "My API") .option("-b, --base-url ", "Base URL", "http://localhost:3000/api") .option("-a, --auth ", "Auth type (bearer|basic|apikey)") .parse();

const options = program.opts();

async function main() { const routes = await scanRoutes(options.framework, options.source);

generateBrunoCollection(routes, { collectionName: options.name, outputDir: options.output, baseUrl: options.baseUrl, authType: options.auth, });

console.log(Generated Bruno collection in ${options.output}); console.log(Open with: bruno run ${options.output}); }

main();

Best Practices Git-friendly: Bruno stores everything as plain text files Use environments: Store URLs and tokens in environment files Secret variables: Mark sensitive vars with vars:secret Add docs: Document each request with the docs block Pre/post scripts: Automate token extraction and setup Add tests: Include assertions in test blocks Organize folders: Group related requests together Sequence numbers: Order requests logically with seq Output Checklist bruno.json manifest created Environment files for dev/staging/prod Folder structure by resource folder.bru for each folder Request .bru files with proper syntax Path parameters use {{param}} syntax Query parameters in query block Request bodies in body:json block Authentication configured Documentation in docs block Pre/post scripts for token handling

返回排行榜