Command line / Automation
Pipelines, reports and the bot
Run the checks on every push, get a review comment on every pull request, and deploy from CI, all with the same commands you use on your machine.
Set up a pipeline in one command
- In your project run
penstock init --ci github. The other choices aregitea,forgejo,gitlab,bitbucketandazure. - Penstock writes the pipeline files, and tells you what to add in your CI settings (a secret, a token).
- Commit the files and push.
The pipeline it writes depends on the licence on the machine that ran the command. Without Team it runs the free check, the drawing and the rules, so its first run does not fail on a feature nobody has bought. With Team it also sets up the review bot, SARIF and baselines. If you buy Team later, run init --ci again. It writes the pipeline files again, over the ones it wrote before, so keep your own changes in a separate file.
| Platform | Files written |
|---|---|
| GitHub | .github/workflows/penstock.yml, penstock-publish.yml, and with Team penstock-bot.yml |
| Gitea, Forgejo | .gitea/workflows/ or .forgejo/workflows/: penstock.yml, or penstock-bot.yml with Team |
| GitLab | penstock.gitlab-ci.yml. Include it from .gitlab-ci.yml |
| Bitbucket | bitbucket-pipelines.penstock.yml. Merge it into bitbucket-pipelines.yml |
| Azure DevOps | azure-pipelines.penstock.yml |
The check in a pipeline
The free workflow on GitHub looks like this:
name: Diagrams
on: [push, pull_request]
jobs:
penstock:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: kern0x1b/penstock-release@<commit>
with:
command: check
init --ci pins the action to a commit, not to a branch, and writes the commit and its date in the file.
The action runs your project's penstockw when there is one, so CI runs exactly the version the project pins, and it caches the download. Inputs: command (check, layout, fmt, deploy or doctor), paths, profile, version, baseline, sarif and args.
With Team, the same step gets the licence from a secret, writes SARIF against the baseline and uploads it to code scanning:
- uses: kern0x1b/penstock-release@<commit>
env:
PENSTOCK_LICENSE: ${{ secrets.PENSTOCK_LICENSE }}
with:
command: check
sarif: penstock.sarif
baseline: 'true'
- uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: penstock.sarif
Get the licence into the pipeline
- On a machine that has a licence activated, in a terminal, run
penstock license key. It signs in with an e-mail code and prints the Team key. - Store the key as a secret named
PENSTOCK_LICENSE.
The key takes no machine's place, and only a Team key works: a Pro key is refused in a pipeline. Penstock uses it only when CI is set, so a laptop that has the variable does not become licensed by it. Each new runner exchanges the key for a token that lasts six hours. If the licence service cannot be reached, the run is Free. See Buying and signing in.
Machine formats
--format sarif | SARIF 2.1.0. GitHub code scanning reads it and shows notes on the pull request. |
--format github | Annotation lines for a workflow that does not use code scanning. |
--format json | For anything else. Every finding carries its rule id and the address of its page. |
SARIF and annotations are Pro on your machine and Team in a pipeline, and cover every section of the check.
Only what is new
A first run on a repository that has drawn diagrams for years is red, and a red build nobody can fix gets switched off. Record what the project already lives with, and check against it from then on.
penstock check . --update-baseline # writes penstock-baseline.json. Commit it.
penstock check . --baseline # fails only on what is new
It also says what was fixed since the baseline was taken, so you can refresh the file when the backlog shrinks. A baseline covers every section. One taken before the check had sections needs refreshing with --update-baseline. Baselines are Pro, and Team in a pipeline.
The file is JSON: the time it was written, and a list of findings, each with the path of the file relative to the project, the rule id and the message. A finding is the same one when all three match. Changing the text of a message, or moving the diagram to another folder, makes it new. penstock migrate analyze has its own baseline, penstock-migration-baseline.json, written by penstock migrate analyze --update-baseline and read with --baseline, so the two lists of accepted findings do not mix.
Before the commit
penstock init --hooks installs a pre-commit hook that formats and checks the diagrams a commit carries, and nothing else. It lives in .git/hooks, so it is yours, not the repository's. Deleting the file removes it. It runs check --baseline, so it needs Pro.
The pull request report
Pro, Team in a pipeline
penstock report writes one Markdown comment for a pull request: the check against the baseline, what changed by element, and pictures with the changes outlined (green arrived, amber differs).
penstock report . --base origin/main --out penstock-report.md
The review bot
Pro, Team in a pipeline
The bot is penstock bot running in your own CI. There is no service behind it and nothing to register. It answers with the token of the pipeline it runs in.
On a pull or merge request that touches a diagram it leaves one comment, and edits that same comment on every push. In the conversation you can ask it:
/penstock the full review
/penstock check only the problems
/penstock diff v1.4.0 what changed since a commit, branch or tag, outlined
/penstock image orders.bpmn a picture of the diagram as it is now
/penstock help
The picture is drawn by Penstock itself without a browser. Nothing is ever committed to the repository.
| Platform | Comments answered | The picture | What to add |
|---|---|---|---|
| GitHub | yes | kept with the run, opens from the link in the comment | Nothing |
| Gitea, Forgejo | yes | attached to the pull request, shown inline | Nothing (or PENSTOCK_TOKEN) |
| GitLab | yes, through a webhook on the pipeline trigger | uploaded to the project, shown inline | PENSTOCK_TOKEN: a project access token, role Developer, scope api |
| Azure DevOps | no, the review only | attached to the pull request, shown inline | Let the build service contribute to pull requests |
| Bitbucket | no, the review only | a pipeline artifact, linked from the comment | PENSTOCK_TOKEN: a repository access token with pull request write |
init --ci prints exactly what to add for your platform. The bot reads the diagrams and penstock.yaml. It never runs hooks that a pull request's configuration might declare.
Asking the bot, and the variables it reads
A comment is read when it mentions /penstock or @penstock. After the mention: nothing or report gives the full review, check or problems only the problems, diff or against with a ref what changed since it, and image, picture or screenshot with a .bpmn path a picture. Anything else gives the help. A pull request from a fork gets a refusal, not a run.
| Variable | What it does |
|---|---|
PENSTOCK_COMMAND | The request, as text, for a run that was not started by a comment, such as a pipeline on a pull request. Set it to /penstock check and the bot answers with the problems only. When it is not set, the answer is the full review. When it does not mention Penstock, the bot says it is not for it and stops. |
PENSTOCK_BOT_DIR | Where the bot keeps what it draws, in a folder called penstock-bot inside it. Without it the bot uses RUNNER_TEMP, and then the system's temporary folder. The folder holds the saved answer, answer.json, and the pictures, stacked in penstock-diagrams.svg. Point it into the workspace when the pictures are to be kept as a pipeline artifact: the pipelines that init --ci writes for Bitbucket and Azure set it. |
PENSTOCK_PICTURES_URL | The address that a comment links to for pictures it cannot show inline. By default it is the page of the run: the run on GitHub, the job's artifacts on GitLab, the pipeline's results on Bitbucket, the build's artifacts on Azure. |
A run can be split in two with --stage draw and --stage post. The first does everything except posting, and saves the answer and the pictures under PENSTOCK_BOT_DIR. The second posts the saved answer. Use it when the pictures have to be uploaded in between.
Deploy from CI
init --ci github also writes a publishing workflow. On merge it runs penstock deploy . --wait with the prod profile:
PENSTOCK_PROFILE=prod \
PENSTOCK_PASSWORD=$CAMUNDA_PASSWORD \
./penstockw deploy . --wait
The profile chooses the environment. The secret arrives through the environment, not through the repository. With auth.type: oauth the client secret comes from PENSTOCK_CLIENT_SECRET. PENSTOCK_TOKEN is used as a bearer token when the engine wants one. Any PENSTOCK_* variable that matches a setting overrides it for the run, for example PENSTOCK_DEPLOYMENT_URL. --wait matters: it asks the engine for the definitions it just accepted until the engine answers. Run --dry-run first in a new pipeline. See Deployment.
Test cases in CI
Mark the test engine with deployment.forTests: true on its profile. Then penstock scenarios run . --junit-xml build/test-cases.xml runs them. See Scenarios.
What you see
- In your CI system, a job called Diagrams (or Penstock bot with Team) runs on every push or pull request. It shows
okorfailfor each file. - On a pull request that touches a diagram, the bot leaves one comment and edits that same comment on every push (Team).
- With SARIF, GitHub shows the findings as notes on the changed lines.
Common problems
- The bot does not answer.
- Check the token setup that
init --ciprinted for your platform, and thatPENSTOCK_LICENSEholds a Team key. Also check that the job hasfetch-depth: 0, so it can compare against the base branch. - The first run of the check is red on an old repository.
- Take a baseline with
--update-baselineand check with--baselinefrom then on. - "Findings in the code review ... is part of Penstock Team".
- SARIF and annotations in a pipeline need Team. Use the plain
check, or add a Team key. - My pipeline downloads Penstock on every run.
- Commit the wrapper (
penstockwand its properties file). The GitHub action then caches~/.penstock/wrapper/dists.