Makefile Over GitHub Actions → Total Control
Problem
We became slaves to platforms. GitHub Actions, CI/CD pipelines, cloud services—they all promise convenience but deliver dependency. You push code, wait for workflows, hope they succeed. You're not in control; the platform is.
The Awakening
Today, I deleted .github/workflows/. All of it. Gone.
Why? Because I realized something fundamental: Platforms are just interfaces. Logic should be ours.
The Makefile Renaissance
What We Had (Platform Slavery)
# .github/workflows/publish.yml
name: Publish Packages
on:
push:
branches: [main]
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm publish
Problems:
- Vendor lock-in to GitHub
- Debugging requires pushing code
- Waiting for runners
- Limited to GitHub's environment
- No local testing
What We Built (Total Control)
# Makefile
publish:
@for dir in $$(fd package.json packages/ -x dirname {}); do \
pkg=$$(jq -r '.name' $$dir/package.json); \
version=$$(jq -r '.version' $$dir/package.json); \
published=$$(npm view $$pkg version 2>/dev/null || echo "0.0.0"); \
if [ "$$version" != "$$published" ]; then \
echo "Publishing $$pkg@$$version..."; \
(cd $$dir && npm publish); \
fi \
done
Benefits:
- Works anywhere (local, CI, anywhere with make)
- Instant execution
- Full transparency
- No external dependencies
- Complete control
The Philosophy
This connects to our core principle: Zero Documentation → Living Code
Now extended: Zero Platform Dependency → Total Control
Before: Interface-Driven Development
Developer → GitHub → Actions → npm
↑ ↑ ↑
Interface Platform Service
Each arrow is a dependency. Each dependency is a potential failure point.
After: Logic-First Development
Developer → Makefile → Direct Execution
↑ ↑
Logic Control
No intermediaries. No waiting. No surprises.
Real Implementation
Here's our actual Makefile that replaced all GitHub Actions:
.PHONY: help readme publish publish-all list clean test
# Generate README dynamically from packages
readme:
@echo "# vibe-coding" > README.md
@echo "" >> README.md
@echo "| Command | Package | Description |" >> README.md
@echo "|---------|---------|-------------|" >> README.md
@for dir in $$(fd package.json packages/ -x dirname {} | sort); do \
name=$$(basename $$dir); \
pkg=$$(jq -r '.name' $$dir/package.json); \
desc=$$(jq -r '.description' $$dir/package.json); \
echo "| [\`$$name\`](packages/$$name) | \`$$pkg\` | $$desc |" >> README.md; \
done
# Version bump all packages
bump:
@for dir in $$(fd package.json packages/ -x dirname {}); do \
pkg=$$(jq -r '.name' $$dir/package.json); \
old=$$(jq -r '.version' $$dir/package.json); \
new=$$(echo $$old | awk -F. '{print $$1"."$$2"."$$3+1}'); \
jq ".version = \"$$new\"" $$dir/package.json > $$dir/package.json.tmp && \
mv $$dir/package.json.tmp $$dir/package.json; \
echo "$$pkg: $$old → $$new"; \
done
# Test all tools
test:
@for dir in $$(fd package.json packages/ -x dirname {}); do \
name=$$(basename $$dir); \
echo "Testing $$name..."; \
(cd $$dir && bun run ./index.ts -h) || true; \
done
The Power of Make
Make is 48 years old (1976). It survived because it follows Unix philosophy:
- Do one thing well
- Everything is a file
- Compose simple tools
- Text is the universal interface
GitHub Actions is 5 years old. It won't survive 48 years. Make will.
Practical Benefits
1. Local-First Development
make test # Test locally
make publish # Publish locally
# No pushing to GitHub to test workflows
2. Platform Independence
Works on:
- Local machine
- Any CI/CD platform
- SSH servers
- Docker containers
- Anywhere with
make
3. Speed
- GitHub Actions: Push → Wait for runner → Execute → Results (2-5 minutes)
- Makefile: Execute → Results (instant)
4. Transparency
make -n publish # See what will run without executing
You can't do this with GitHub Actions.
The Broader Pattern
This is part of a larger realization:
Platforms are interfaces → Control the logic
Services are conveniences → Own the capability
Clouds are computers → Your computer is enough
Workflows are just scripts → Write scripts
Migration Guide
Step 1: Identify Platform Dependencies
- GitHub Actions → Makefile
- Vercel → Static hosting
- AWS Lambda → Local server
- Heroku → Systemd service
Step 2: Extract the Logic
What does the platform actually do? Write it yourself.
Step 3: Create Local-First Tools
Every cloud service can be replaced with:
- A script
- A Makefile target
- A local service
- A simple binary
Real-World Examples
Dynamic README Generation
Instead of a static README that gets outdated:
readme:
@for package in packages/*; do \
echo "- [$$(basename $$package)]($$package)" >> README.md; \
done
Auto-Publishing
Instead of GitHub Actions:
publish: readme
@git diff --exit-code || (git add -A && git commit -m "auto: update" && git push)
@make publish-npm
Continuous Testing
Instead of CI/CD:
watch:
@fswatch -o . | xargs -n1 -I{} make test
The Unix Way Wins
We returned to basics:
Make instead of CI/CD platforms
Shell scripts instead of workflows
Git hooks instead of GitHub Apps
Cron instead of scheduled actions
Each tool does one thing well. Each tool is under our control.
Conclusion
Deleting .github/workflows/ was liberating. We're no longer waiting for runners, debugging YAML, or tied to GitHub's infrastructure.
The Makefile is our declaration of independence. It's code we control, logic we own, and automation that works everywhere.
Stop configuring platforms. Start writing Makefiles.
The best workflow is no workflow—just make.
Part of the Zero Documentation → Living Code philosophy. See also: @yemreak/culture - our tool for discovering patterns from code instead of reading documentation.