Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
5f61a09
chore(integrations): scaffold the integrations sub-tree
arpit-bruno Sep 14, 2026
68b22c4
node core options, types and the hardened collection walk
arpit-bruno Sep 14, 2026
b516c2b
collection filters: environments by name, requests by tag
arpit-bruno Sep 14, 2026
c50ce91
resolve the source, build once, serve from memory
arpit-bruno Sep 14, 2026
fcad882
shell: one browser half for every language core
arpit-bruno Sep 15, 2026
a0a0b52
core: request handler, createDocs, and the routes directory
arpit-bruno Sep 15, 2026
fa7ff0b
adds: express framework integratiins and the shared fixture and a co…
arpit-bruno Sep 17, 2026
87e0c41
fastify plugin and example, plus host-isolation assertions in check.sh
arpit-bruno Sep 17, 2026
2914735
nestjs module and example, running on both adapters
arpit-bruno Sep 17, 2026
9189ec0
one command for lint, build, seven suites for four frameworks
arpit-bruno Sep 17, 2026
0375c6c
browser specs, the CSP directives the renderer's wasm needs
arpit-bruno Sep 17, 2026
88d926b
path-gated ci jobs, a framework matrix with docker mode and nest 12 s…
arpit-bruno Sep 17, 2026
0ef2403
example sets per framework
arpit-bruno Sep 17, 2026
3558e59
adds test runner with a matrix table
arpit-bruno Sep 18, 2026
2809aa8
reject unknown options and coverage for:413, 500, logo and the page i…
arpit-bruno Sep 18, 2026
7c98f23
reports four test layers separately
arpit-bruno Sep 18, 2026
127723a
fix folders lost in assembly and the fixture's auth
arpit-bruno Sep 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 95 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,19 @@ on:



permissions:
contents: read
pull-requests: read
checks: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
lint_unit_tests_and_builds:
needs: changes
if: needs.changes.outputs.renderer == 'true'
runs-on: ubuntu-latest
steps:
# eslint-plugin-diff needs the base branch present to diff against.
Expand Down Expand Up @@ -43,8 +50,8 @@ jobs:
run: npm run build:standalone -w packages/bruno-api-docs

e2e:
needs: lint_unit_tests_and_builds
if: github.event_name == 'push' || github.event.pull_request.draft == false
needs: [changes, lint_unit_tests_and_builds]
if: needs.changes.outputs.renderer == 'true' && (github.event_name == 'push' || github.event.pull_request.draft == false)
runs-on: ubuntu-latest
defaults:
run:
Expand All @@ -70,3 +77,88 @@ jobs:
name: playwright-report
path: packages/bruno-api-docs/playwright-report/
retention-days: 30

changes:
runs-on: ubuntu-latest
outputs:
renderer: ${{ steps.filter.outputs.renderer }}
integrations: ${{ steps.filter.outputs.integrations }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@0e4a8c6effa4802afeda77dc8d303f8176d7dfad # v3
id: filter
with:
filters: |
renderer:
- '!integrations/**'
integrations:
- 'integrations/**'
- '.github/workflows/ci.yml'
- '.github/workflows/integrations-matrix.yml'

integrations:
needs: changes
# a job-level if, not a workflow paths filter: a skipped job still reports and satisfies the required check
if: needs.changes.outputs.integrations == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: integrations
env:
FORCE_COLOR: 1
JUNIT_DIR: results
steps:
- uses: actions/checkout@v4

- uses: jdx/mise-action@v2
with:
working_directory: integrations

- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-integrations-${{ hashFiles('integrations/package-lock.json') }}
restore-keys: npm-integrations-

- run: npm ci

- name: lint
run: npm run lint

- name: build
run: npm run build

- name: unit
run: bash nodejs/scripts/unit.sh

- name: contract, every framework byte for byte
run: bash nodejs/scripts/check-all.sh

- name: examples
run: bash contract-tests/examples.sh

- name: one matrix cell, from packed tarballs
run: bash nodejs/scripts/matrix.sh --cell express@5 --results results

# every assertion in the Checks tab, failures as annotations
- uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
if: always()
with:
name: integrations
path: integrations/results/**/*.xml
reporter: java-junit
fail-on-error: false

# both tiers depend on the live CDN bundle, which this repo does not control, so neither can
# block a merge. `contract` is ours to fix when red; `renderer` is theirs to know about.
- name: browser
continue-on-error: true
run: npx playwright install --with-deps chromium

- name: browser, our contract
continue-on-error: true
run: npx playwright test --config contract-tests/browser/playwright.config.mjs --project=contract

- name: browser, the renderer
continue-on-error: true
run: npx playwright test --config contract-tests/browser/playwright.config.mjs --project=renderer
124 changes: 124 additions & 0 deletions .github/workflows/integrations-matrix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Integrations matrix

on:
workflow_dispatch:
schedule:
- cron: '0 6 * * 1'
pull_request:
branches: [main, 'release/**']
types: [labeled, synchronize]

permissions:
contents: read
pull-requests: write
checks: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

env:
FORCE_COLOR: 1

jobs:
cells:
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-integrations-matrix-tests')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [22, 24]
family: [express, fastify, nestjs]
name: node ${{ matrix.node }} / ${{ matrix.family }}
defaults:
run:
working-directory: integrations
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

- run: npm ci

- run: bash nodejs/scripts/matrix.sh --family ${{ matrix.family }} --results results

- uses: actions/upload-artifact@v4
if: always()
with:
name: matrix-${{ matrix.node }}-${{ matrix.family }}
path: integrations/results

node-20:
if: github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'run-integrations-matrix-tests')
runs-on: ubuntu-latest
continue-on-error: true
name: node 20 (end of life, courtesy)
defaults:
run:
working-directory: integrations
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: bash nodejs/scripts/matrix.sh --results results
- uses: actions/upload-artifact@v4
if: always()
with:
name: matrix-20-all
path: integrations/results

report:
needs: [cells, node-20]
if: always() && needs.cells.result != 'skipped'
runs-on: ubuntu-latest
name: results table
defaults:
run:
working-directory: integrations
steps:
- uses: actions/checkout@v4

- uses: actions/download-artifact@v4
with:
pattern: matrix-*
merge-multiple: true
path: integrations/results

- run: |
{
echo "## Integrations matrix"
echo
bash nodejs/scripts/matrix.sh --report results --md
echo
echo "_${{ github.sha }} · node 20 is informational_"
} > table.md
cat table.md >> "$GITHUB_STEP_SUMMARY"

# every assertion in the Checks tab, failures as annotations
- uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
with:
name: integrations matrix
path: integrations/results/**/*.xml
reporter: java-junit
fail-on-error: false

# one comment per PR, updated in place on every run
- if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
PR: ${{ github.event.pull_request.number }}
run: |
marker='<!-- integrations-matrix -->'
printf '%s\n' "$marker" > body.md
cat table.md >> body.md
existing=$(gh api "repos/$GITHUB_REPOSITORY/issues/$PR/comments" --paginate \
--jq ".[] | select(.body | startswith(\"$marker\")) | .id" | head -1)
if [ -n "$existing" ]; then
gh api -X PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$existing" -F body=@body.md >/dev/null
else
gh pr comment "$PR" --body-file body.md
fi
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ test.js

# Claude Code
.claude/
CLAUDE.md
CLAUDE.md
test-results
!integrations/contract-tests/fixtures/walk-safety/node_modules/
8 changes: 8 additions & 0 deletions integrations/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# the container installs its own: esbuild and playwright ship native binaries built for this host
node_modules
nodejs/*/node_modules
nodejs/*/dist
shell/shell.js
nodejs/*/shell
test-results
nodejs/nestjs/examples/dist
3 changes: 3 additions & 0 deletions integrations/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# build output, and the copy of it that each language core publishes
shell/shell.js
*/*/shell/
9 changes: 9 additions & 0 deletions integrations/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Bruno API Docs — framework integrations

Serve a Bruno collection as API docs from inside your own app, behind your own auth.

```sh
cd integrations
npm ci
npm run lint
```
67 changes: 67 additions & 0 deletions integrations/contract-tests/apps/express.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
'use strict';

const express = require('express');
const helmet = require('helmet');
const { apiDocs, embed } = require('@usebruno/api-docs-express');

const PORT = Number(process.env.PORT || 5456);
// relative on purpose: the core resolves it against this file, not the cwd, and check.sh boots
// from a different directory to prove it
const COLLECTION = '../fixtures/api-collection';
const BUNDLED = '../fixtures/bundled.yml';

const app = express();

// the CSP the README documents: 'self' covers shell.js, the CDN serves the renderer, and the
// renderer's wasm sandbox needs 'wasm-unsafe-eval' to instantiate and data: to be fetched
app.use(
helmet({
contentSecurityPolicy: {
directives: {
scriptSrc: ["'self'", 'https://cdn.usebruno.com', "'wasm-unsafe-eval'"],
styleSrc: ["'self'", "'unsafe-inline'", 'https://cdn.usebruno.com', 'https://fonts.googleapis.com'],
fontSrc: ["'self'", 'https://fonts.gstatic.com'],
connectSrc: ["'self'", 'data:']
}
}
})
);

app.get('/control', (req, res) => res.json({ ok: true, from: 'the app itself' }));

app.use('/docs', apiDocs({
collection: COLLECTION,
environments: { include: ['Local'] },
tags: { exclude: ['internal'] },
pageTitle: 'Acme API',
logo: 'data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 10 10%22%3E%3Ccircle cx=%225%22 cy=%225%22 r=%225%22/%3E%3C/svg%3E',
gitCollectionUrl: 'https://token:secret@github.com/acme/api-collection'
}));

app.use('/api/v2/docs', apiDocs({ collection: COLLECTION, environments: { all: true, exclude: ['Prod'] } }));

app.use('/internal/docs', apiDocs({ collection: COLLECTION }));

app.use('/bundled/docs', apiDocs({ collection: BUNDLED }));

app.use('/broken/docs', apiDocs({ collection: './there-is-no-collection-here' }));

app.use('/oversize/docs', apiDocs({ collection: '../fixtures/walk-oversize' }));

// theme is what the renderer will take next; until it does, passing it is a mistake we report
app.use('/misconfigured/docs', apiDocs({ collection: COLLECTION, theme: 'dark' }));

// the docs inside the host's own page: the mount serves, the block only points at it
app.use('/portal/docs', apiDocs({ collection: COLLECTION, environments: { include: ['Local'] } }));
app.get('/portal', (req, res) => {
res.type('html').send(`<!doctype html>
<html lang="en"><head><meta charset="utf-8"><title>Acme Developer Portal</title></head>
<body>
<h1>Acme Developer Portal</h1>
${embed({ base: '/portal/docs' })}
</body></html>`);
});

app.listen(PORT, () => {
console.log(`express rig on http://localhost:${PORT}`);
});
Loading
Loading