Introduction

Laravel already gives for web project several command surfaces: Artisan, Composer scripts, npm scripts, and even a small admin-facing interface for a restricted set of Artisan commands. So the obvious question is why this repository still keeps a top-level Makefile.

During development, many other commands are still needed, either as commands from the terminal or in the form of bash (zsh) scripts. The sum of all such commands and their structure brings problems:

  • with remembering their definition
  • with the length of the command line (requiring many keystrokes on the keyboard)
  • with executing the command in the right place (directory) in the application structure A very practical solution is to use "make" as a shell language to support development acting as a thin coordination layer over the commands the developer clearly runs often: route inspection, cache resets, log cleanup, grouped test execution, static analysis, and a few environment-specific checks.

It means the Makefile is the approved command surface for repetitive development work inside the editor.

This article walks through what the current Makefile actually does, why these targets exist, and what the file says about how this Laravel project is operated.

So the Makefile is not just convenience glue. It expresses which operations are considered normal for daily development, and which stay outside that surface.

Practical example of a Makefile used for development

The described Makefile is a shortened example of the most commonly used commands when developing Laravel applications using Docker or VirtualBox on Windows 11. The Makefile below is used in the Debian 13 environment for different workspaces (development, preparation for deployment, staging). For this reason, shared files tools.mk (common commands for all workspaces) and tools-special.mk are used.

Makefile for development workspace

## Makefile for usefull scripts in Laravel11 Blog project
## cmd:  make
.PHONY: help copy test module

CURRENT=$(shell date +'_%g%m%d')
export STAMP:=$(CURRENT)

# Include common targets from tools*.mk
include /var/www/tools.mk
include /var/www/tools-special.mk

checklinks: ## check for broken links in the project (install broken-link-checker globally with npm install -g broken-link-checker)
	npx broken-link-checker http://192.168.0.175:81 --recursive --ordered --filter-level 2

list: ## show routes (all - no param | filter - add param: name="crop")
	@if [ -n "$(name)" ]; then \
		php artisan route:list --name "$(name)"; \
	else \
		php artisan route:list; \
	fi

listv: ## show all routes incl. middleware (all - no param | filter - add param: name="crop")
	@if [ -n "$(name)" ]; then \
		php artisan route:list --name "$(name)" --verbose; \
	else \
		php artisan route:list --verbose; \
	fi

rlog: ## reset laravel log to empty content
	truncate -s 0 storage/logs/laravel.log

rc: ## reset all laravel caches
	php artisan optimize:clear

run: ## run phpunit tests for group run
	vendor/bin/phpunit --group run --testdox

runz: ## run phpunit tests for group runz (all - no param | filter - add param e.g. filter="unzip")
	@if [ -n "$(filter)" ]; then \
		vendor/bin/phpunit --group runz --filter "$(filter)" --testdox; \
	else \
		vendor/bin/phpunit --group runz --testdox; \
	fi  

stan: ## run phpstan
	./vendor/bin/phpstan analyse

tools.mk

## Unified Makefile — Common targets Generated: 2026-04-03

.PHONY: help chown rc list listv run runz testm scomposer crm cpkg vscext stan

CURRENT=$(shell date +'_%g%m%d')
export STAMP:=$(CURRENT)

help: ## Tools makefile targets HELP
	@echo ""
	@echo "Makefile for useful scripts in Laravel11 Blog project"
	@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n  make \033[36m<target>\033[0m\n\nTargets:\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf "  \033[36m%-10s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST)

---: ## COMMON TARGETS

chown: ## set all project files for owner 'www-data:www-data' with 755
	chown -R www-data:www-data .
	chmod -R 755 .

chownacl: ## set all project files for owner 'www-data:www-data' with ACL
	setfacl -R -m u:www-data:rwx .
	setfacl -R -m d:u:www-data:rwx .
	setfacl -R -m u:vacal:rwx .
	setfacl -R -m d:u:vacal:rwx .

size: ## show disk usage of current folder in human readable format with depth 1
	du -ha -d 1 | sort -h

tools-special.mk

## Unified Makefile — Special targets (unique usage)
## Generated: 2026-04-04

---: ## SPECIAL TARGETS (development)

websnap: ## create web snapshot with mobile-check.mjs script
	node tests/web/scripts/mobile-check.mjs

webqa: ## run mobile-qa-report.mjs tests (Mobile QA Checklist)
	node tests/web/scripts/mobile-qa-report.mjs

webtap: ## run small tap target tests with check-tap-targets.js
	node tests/web/check-tap-targets.js

That structure tells you several things immediately:

  1. Only application-specific commands stay in the local Makefile.
  2. The file is designed to be read by humans, not just executed by CI.

The shared include layer is important here. The loaded common makefile provides the help target, so plain make functions more like an operational menu than a build step.

Here is terminal output for command make:

Makefile for useful scripts in Laravel11 Blog project

Usage:
  make <target>

Targets:
  checklinks  check for broken links in the project (install broken-link-checker globally with npm install -g broken-link-checker)
  list        show routes (all - no param | filter - add param: name="crop")
  listv       show all routes incl. middleware (all - no param | filter - add param: name="crop")
  rlog        reset laravel log to empty content
  rc          reset all laravel caches
  run         run phpunit tests for group run
  runz        run phpunit tests for group runz (all - no param | filter - add param e.g. filter="unzip")
  stan        run phpstan
  testm       create module (Feature) test with test="<TestName>" module="<ModuleName>"
  help        Tools makefile targets HELP
  ---         COMMON TARGETS
  chown       set all project files for owner 'www-data:www-data' with 755
  chownacl    set all project files for owner 'www-data:www-data' with ACL
  size        show disk usage of current folder in human readable format with depth 1
  vscext      create vscode-extensions.txt file with all installed extensions
  ---         SPECIAL TARGETS (development)
  scomposer   run composer as www-data user
  crm         remove existing package from composer
  cpkg        copy vendor package importobsidian to /packages/jotcomp
  websnap     create web snapshot with mobile-check.mjs script
  webqa       run mobile-qa-report.mjs tests (Mobile QA Checklist)
  webtap      run small tap target tests with check-tap-targets.js

Remarks to Testing Commands

The most revealing part of the Makefile is the testing split:

run: ## run phpunit tests for group run
	vendor/bin/phpunit --group run --testdox

runz: ## run phpunit tests for group runz (all - no param | filter - add param e.g. filter="unzip")
	@if [ -n "$(filter)" ]; then \
		vendor/bin/phpunit --group runz --filter "$(filter)" --testdox; \
	else \
		vendor/bin/phpunit --group runz --testdox; \
	fi

This is not generic Laravel practice. It is repository-specific and based on PHPUnit group attributes.

The existing suite contains many tests marked with Group('run') across controllers, services, models, security, and ZIP-import-related flows. That makes make run the stable test lane (for all approved tests).

The runz lane appears in repository prompt guidance for new feature developemnt work. New or modified tests are expected to carry Group('runz') so they can be validated quickly with make runz during active development. At the moment, the repository visibly contains many run tests but no current runz annotations in the inspected test tree. That suggests runz is intended as a fast-moving work-in-progress lane rather than a permanently populated bucket.

The two-lane setup gives the maintainer both a stable baseline and a place for focused current-work verification.

Summary

This project's Makefile is not an alternative interface to Laravel. It is a small operational index for the commands that matter most in this specific repository.

Its value comes from four things:

  1. It standardizes the commands the project actually uses.
  2. It encodes repository policy around testing and editor-safe workflows.
  3. It reflects the real shape of the application: admin-heavy, package-extended, and operationally maintained.
  4. It accepts environment-specific assumptions instead of hiding them behind fake portability.

A useful Makefile does not need to be universal. It needs to reduce friction in the environment where the work actually happens. In this repository, that is exactly what it does.