Skip to content

feat(agent-bff): emit the OpenAPI document from a forest-bff openapi subcommand - #1811

Open
Tonours wants to merge 1 commit into
feature/prd-886-openapi-static-documentfrom
feature/prd-887-openapi-cli-export
Open

feat(agent-bff): emit the OpenAPI document from a forest-bff openapi subcommand#1811
Tonours wants to merge 1 commit into
feature/prd-886-openapi-static-documentfrom
feature/prd-887-openapi-cli-export

Conversation

@Tonours

@Tonours Tonours commented Aug 6, 2026

Copy link
Copy Markdown
Member

Stacked on #1810 (feature/prd-886-openapi-static-document), which is itself stacked on #1809. The base is #1810's branch, not main: both parents must be reviewed and merged first, in order (#1809, then #1810, then this). The diff below is only this branch's commit.

Adds forest-bff openapi: writes the OpenAPI document to stdout, or to a file with --output, without booting the server.

fixes PRD-887

Why a CLI at all

GET /agent/openapi.json is auth-gated, so a CI job, a codegen step, or a client dev without credentials has no HTTP path to the document. The CLI is that path. It needs no configuration:

forest-bff openapi > openapi.json          # stdout
forest-bff openapi --output                # writes ./openapi.json
forest-bff openapi --output docs/api.json  # writes that path, creating docs/

argv contract

Previously forest-bff <anything> ignored argv and booted the server. Now argv is parsed:

Invocation Before After
forest-bff boots the server boots the server, unchanged
forest-bff openapi booted the server document on stdout, exit 0
forest-bff openapi --output [path] booted the server writes the file, exit 0
forest-bff --help / -h booted the server usage on stdout, exit 0
forest-bff --version / -v booted the server bare version on stdout, exit 0
forest-bff bogus booted the server exit 1, reason on stderr
forest-bff openapi extra booted the server exit 1, names the extra

--help and --version print to stdout, exit 0, and ignore what follows them: GNU tools treat a help request as a success, never an error. openapi is the strict one, because a typo there would otherwise produce a wrong export instead of a message.

A deployment that passed a stray flag and silently got a server will now fail on upgrade. The repo's own start scripts pass no arguments.

--output parsing

The token after --output is the destination only when it exists, is non-empty, and does not start with -. Otherwise the default openapi.json applies and the leftover token is rejected as an extra. So openapi --output --help exits 1 and writes nothing, rather than creating a file named --help.

Extras are validated before any write, so an invalid command line never leaves an artifact behind. --output=path and a repeated --output are rejected the same way.

A missing parent directory is created. An existing file is overwritten, like > and every codegen exporter. An unwritable destination exits 1 with Cannot write <path>: <reason>, no stack trace.

The success line goes to stderr, not stdout: --output must leave stdout empty so the no-flag form stays pipeable, and so a CI job can keep redirecting without catching a log line.

Stdout purity

An export piped to a file must contain only the document. This is easy to break silently: createConsoleLogger sends Info to console.info, which writes to stdout. Any future log on the export path would corrupt every pipe without failing anything.

Verified rather than assumed: a test pins one single process.stdout.write call and asserts console.info is never reached. The export path never calls parseConfig, never constructs a logger, never binds a socket, and process.exitCode is set instead of calling process.exit(), so a slow pipe cannot truncate the write.

Scope and safety

The no-argv path is untouched: same runCli, same config parsing, same middleware chain. Hand-rolled argv switch in its own cli-dispatch.ts, no arg-parsing dependency.

openapi.json is added to .gitignore: --output has a default filename, and a dev running it from the package would otherwise commit the artifact by accident.

How to test

yarn workspace @forestadmin/agent-bff test
yarn workspace @forestadmin/agent-bff lint

cd packages/agent-bff
env -i PATH="$PATH" node dist/cli.js openapi | npx @redocly/cli lint -   # valid, no config
node dist/cli.js openapi --output && cat openapi.json | head -3          # 0, file written
node dist/cli.js openapi --output --help; echo $?                        # 1, no file created
node dist/cli.js --output; echo $?                                       # 1, points at openapi

Ran on this branch: 809 tests pass (777 before, 32 added), lint and build clean.

An end-to-end script drove the packaged binary through 40 checks, all passing: every argv path with its exit code and stream, the export under an empty environment, byte equality between the CLI export and the body the route serves, no socket bound, no truncation through a real pipe and a real redirect, --output writing the default name and an explicit path, a created parent directory, a dash token refused as a filename, and a directory destination failing with its path named.

Known limitations

DispatchOutcome.exitCode is typed number and the type admits {exitCode: 1, server}, a combination the code never produces. Harmless today since the only consumer reads exitCode alone.

--output writes with writeFileSync, which truncates before writing. A mid-write failure such as ENOSPC leaves a partial file, same as > and curl -o.

Definition of Done

General

  • Write an explicit title for the Pull Request, following Conventional Commits specification
  • Test manually the implemented changes
  • Validate the code quality (indentation, syntax, style, simplicity, readability)

Security

  • Consider the security impact of the changes made

@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

PRD-887

@qltysh

qltysh Bot commented Aug 6, 2026

Copy link
Copy Markdown

1 new issue

Tool Category Rule Count
qlty Structure Function with many returns (count = 7): dispatchCli 1

@Tonours
Tonours force-pushed the feature/prd-886-openapi-static-document branch from ece634c to b11c332 Compare August 6, 2026 20:48
@Tonours
Tonours force-pushed the feature/prd-887-openapi-cli-export branch 3 times, most recently from 92ca6d4 to 4312e31 Compare August 6, 2026 21:01
Comment thread packages/agent-bff/README.md Outdated
@Tonours
Tonours force-pushed the feature/prd-887-openapi-cli-export branch from 4312e31 to bacd081 Compare August 7, 2026 07:28
@qltysh

qltysh Bot commented Aug 7, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

Modified Files with Diff Coverage (1)

RatingFile% DiffUncovered Line #s
New Coverage rating: A
packages/agent-bff/src/cli-dispatch.ts100.0%
Total100.0%
🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@Tonours
Tonours force-pushed the feature/prd-886-openapi-static-document branch from d51081e to 9652148 Compare August 7, 2026 07:42
@Tonours
Tonours force-pushed the feature/prd-887-openapi-cli-export branch from bacd081 to db6ff62 Compare August 7, 2026 07:42
@Tonours
Tonours force-pushed the feature/prd-886-openapi-static-document branch from 9652148 to ec8c3e0 Compare August 7, 2026 13:01
@Tonours
Tonours force-pushed the feature/prd-887-openapi-cli-export branch 6 times, most recently from f078ea2 to 56858d7 Compare August 7, 2026 15:38
@Tonours
Tonours force-pushed the feature/prd-887-openapi-cli-export branch from 56858d7 to 04f7775 Compare August 7, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant