Developer vs. Pipelines – Saving frustration

There are few things more frustrating for developers than waiting for pipelines.

You code, you commit, you open a pull request, and then you sit there watching the spinning wheel. A few minutes later the result pops up – red. Back you go, fixing a detail, pushing again, waiting again. Sometimes this cycle repeats three or four times before it finally turns green. By then you’ve lost not only valuable time but also a good chunk of your motivation and flow.

It doesn’t have to be like that. Over the years, I’ve experimented with different ways to bridge the gap between developers and pipelines. What I wanted was simple: a workflow that makes life easier for developers while still keeping pipelines reliable, fast, and consistent.

The solution turned out to be a surprisingly small one. One command that I now use in almost every project:

make pr

This little command completely changes the game. Instead of waiting for CI to tell me that something is broken, I already know before I even open the pull request. The idea is to run the exact same tools that the pipeline would use, but locally, in the same configuration, and in the same order.

First, it triggers all the fixers – ESLint, Prettier, PHP-CS-Fixer, whatever the project is using. They run in fix mode, automatically correcting formatting issues or small style problems. Once that is done, the validators follow: linters, unit tests, coverage checks, everything that really needs to pass.

The result? By the time I’m ready to push, I already know the pipeline will be green. And if the fixers have changed some files, I can simply amend or commit those changes before creating the PR. No surprises, no wasted cycles, no frustration.

Why I use Make as the foundation

There are plenty of ways to script something like this. Bash scripts, Node scripts, Git hooks – the list is endless. But I’ve found make to be the simplest and most universal option. It works across languages and technology stacks, and most importantly, it creates a common language between developers and pipelines.

Instead of remembering the exact CLI command for PHPUnit with coverage flags, or the right way to invoke ESLint with its config file, I just run:

make phpunit
make eslint
make vitest

Each of these targets wraps the tool with its proper configuration and parameters. In some cases, a single command even bundles multiple steps – for example, running PHPUnit and then checking the coverage threshold against a configured minimum. That’s something you don’t want to retype every time, and make makes sure nobody does it differently by accident.

# this command runs phpunit with additional configurations that you may not want or can add to the configuration
# then it also runs the additional tool 'coverage-check' against the phpunit results

phpunit:  
XDEBUG_MODE=coverage php vendor/bin/phpunit --coverage-html ./.reports/phpunit/html --log-junit=./.reports/phpunit/junit/index.xml --coverage-clover ./.reports/phpunit/clover/index.xml
php vendor/bin/coverage-check ./.reports/phpunit/clover/index.xml $(PHPUNIT_MIN_COVERAGE)

This approach has another big advantage: what runs locally can be used 1:1 in the pipeline. As a DevOps engineer, I don’t need to invent new commands or worry about someone having a different setup. The same make target can run on the developer’s laptop and inside CI, producing the exact same result. That’s how you avoid the dreaded “works on my machine” moments.

Review vs. PR – two approaches

When you start building such a system, you quickly face a choice. Do you want one single target that runs everything, or do you want each tool to have its own dedicated command?

The first approach is to create something like make review. This command simply chains all the checks together, one after the other.

review:
  make phpunit
  make eslint
  make vitest

Locally, that’s easy to use and ensures everything runs in one go. The downside becomes clear in CI pipelines: nothing can run in parallel. Each job waits for the previous one, and that makes pipelines slower than they need to be.

The second approach is to give every tool its own target. That way, the pipeline can execute them in parallel, making it much faster overall.

Locally, however, you don’t want to type five or six commands every time before a pull request. So you need 1 single command. However, instead of only reviewing the code with a command, we might also want to run fixers and more. Therefore we can skip the make review command and create a new one called make pr. It combines the best of both worlds: for developers, one simple entry point; for pipelines, still the freedom to run things in parallel. by executing each tool on its own.

What does it look like in practice?

Here’s a simplified example of such a Makefile. A full version can be found in the Mollie Shopware6 repository, which we maintain.

prettier: ##3 Starts the Prettier
ifndef mode
    ./node_modules/.bin/prettier ./src/Resources/app/ --check
endif
ifeq ($(mode), fix)
    ./node_modules/.bin/prettier ./src/Resources/app/ --write
endif

phpunuhi: ##3 Tests and verifies all plugin snippets
    php vendor/bin/phpunuhi validate --report-format=junit --report-output=./.phpunuhi/junit.xml

# -------------------------

pr:
  make prettier mode=fix
  # -------------------
  # test tools again, because multiple fixers could interfere with each other
  make prettier
  make phpunuhi

From a developer’s perspective, this boils down to a single step:

make pr

That’s it. Behind the scenes, the fixers clean up the code, the validators check the quality, and the developer is left with either a green result or clear feedback on what needs to be fixed – without ever having to wait for CI.

The bigger picture

What I like about this setup is how it balances the needs of developers and DevOps. Developers don’t want to think about pipelines; they just want a simple, predictable way to know their code is ready. DevOps, on the other hand, need flexibility, parallel execution, and reproducibility. With this model, both sides get what they want. Developers have make pr as their daily driver, while pipelines keep their efficiency and accuracy.

And perhaps most importantly: it lowers the barrier to entry. New developers in a project don’t have to ask, “How do I run the tests? How do I check linting? What about coverage?” The answer is always the same. make pr (or separate tool commands). And that’s powerful.

Conclusion

At the end of the day, this workflow saves hours of wasted time, prevents frustration, and creates confidence. If your code is green locally, you can be pretty sure the pipeline will be green too. No more endless red-green loops, no more waiting just to find out what you already could have known earlier.

Sometimes, the best solutions are the simplest. In this case, it’s a single command. make pr